工厂模式

工厂模式概述

意义

  • 问题引出
    在面向对象编程中, 最通常的方法是一个new操作符产生一个对象实例,new操作符就是用来构造对象实例的。当我们使用 new 来构造一个新的类实例时,其实是告诉了 JVM 我需要一个新的实例。JVM 就会自动在内存中开辟一片空间,然后调用构造函数来初始化成员变量,最终把引用返回给调用方。
    但是在一些情况下, new操作符直接生成对象会带来一些问题。举例来说, 许多类型对象的创造需要一系列的步骤: 你可能需要计算或取得对象的初始设置; 选择生成哪个子对象实例; 或在生成你需要的对象之前必须先生成一些辅助功能的对象。在这些情况,新对象的建立就是一个 “过程”,不仅是一个操作,像一部大机器中的一个齿轮传动。
  • 解决的问题
    如何能轻松方便地构造对象实例,而不必关心构造对象实例的细节和复杂过程呢?
  • 一个核心思想
    在所有的工厂模式中,两个类A和B之间的关系应该仅仅是A创建B或是A使用B,而不能两种关系都有。
    将对象的创建和使用分离,也使得系统更加符合“单一职责原则”,有利于对功能的复用和系统的维护。

概念

GOF为工厂模式的定义:

“Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.”
在基类中定义创建对象的一个接口,让子类决定实例化哪个类。工厂方法让一个类的实例化延迟到子类中进行。

  • 用工厂方法代替new操作来实例化对象
  • 定义一个接口来创建对象,但是让子类来决定哪些类需要被实例化

适用场合

  • 一组类似的对象需要创建。
  • 在编码时不能预见需要创建哪些种类的实例。
  • 系统需要考虑扩展性,不应依赖于产品类实例如何被创建、组合和表达的细节。

分类

  1. 简单工厂(Simple Factory)模式,又称静态工厂方法模式(Static Factory Method Pattern)。
  2. 工厂方法(Factory Method)模式,又称多态性工厂(Polymorphic Factory)模式或虚拟构造子(Virtual Constructor)模式;
  3. 抽象工厂(Abstract Factory)模式,又称工具箱(Kit 或Toolkit)模式。

联系

这三种模式从上到下逐步抽象,并且更具一般性。
GOF在《设计模式》一书中将工厂模式分为两类:工厂方法模式(Factory Method)与抽象工厂模式(Abstract Factory)。
将简单工厂模式(Simple Factory)看为工厂方法模式的一种特例,两者归为一类。

区别

工厂方法模式:
一个抽象产品类,可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式:
多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
一个抽象工厂类,可以派生出多个具体工厂类。
每个具体工厂类可以创建多个具体产品类的实例。

简单工厂模式(静态工厂方法)

定义

定义一个工厂类,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。

地位

简单工厂模式并不是23种常用的设计模式之一,它只算工厂模式的一个特殊实现。简单工厂模式在实际中的应用相对于其他2个工厂模式用的还是相对少得多,因为它只适应很多简单的情况。违背了我们在概述中说的 开放-封闭原则 ,每次要新添加一个功能,都需要在生switch-case 语句(或者if-else 语句)中去修改代码,添加分支条件。

考虑使用静态工厂方法代替构造器

Effective Java中考虑使用静态工厂方法代替构造器,其原因作者总结了 4 条(第二版):

1 静态工厂方法与构造器不同的第一优势在于,它们有名字

由于语言的特性,Java 的构造函数都是跟类名一样的。这导致的一个问题是构造函数的名称不够灵活,经常不能准确地描述返回值,在有多个重载的构造函数时尤甚,如果参数类型、数目又比较相似的话,那更是很容易出错。

比如,如下的一段代码 :

Date date0 = new Date();
Date date1 = new Date(0L);
Date date2 = new Date("0");
Date date3 = new Date(1,2,1);
Date date4 = new Date(1,2,1,1,1);
Date date5 = new Date(1,2,1,1,1,1);

—— Date 类有很多重载函数,对于开发者来说,假如不是特别熟悉的话,恐怕是需要犹豫一下,才能找到合适的构造函数的。而对于其他的代码阅读者来说,估计更是需要查看文档,才能明白每个参数的含义了。

(当然,Date 类在目前的 Java 版本中,只保留了一个无参和一个有参的构造函数,其他的都已经标记为 @Deprecated 了)

而如果使用静态工厂方法,就可以给方法起更多有意义的名字,比如前面的 valueOfnewInstancegetInstance 等,对于代码的编写和阅读都能够更清晰。

2.2 第二个优势,不用每次被调用时都创建新对象

这个很容易理解了,有时候外部调用者只需要拿到一个实例,而不关心是否是新的实例;又或者我们想对外提供一个单例时 —— 如果使用工厂方法,就可以很容易的在内部控制,防止创建不必要的对象,减少开销。

在实际的场景中,单例的写法也大都是用静态工厂方法来实现的。

2.3 第三个优势,可以返回原返回类型的子类

这条不用多说,设计模式中的基本的原则之一——『里氏替换』原则,就是说子类应该能替换父类。
显然,构造方法只能返回确切的自身类型,而静态工厂方法则能够更加灵活,可以根据需要方便地返回任何它的子类型的实例

Class Person {
    public static Person getInstance(){
        return new Person();
        // 这里可以改为 return new Player() / Cooker()
    }
}
Class Player extends Person{
}
Class Cooker extends Person{
}

比如上面这段代码,Person 类的静态工厂方法可以返回 Person 的实例,也可以根据需要返回它的子类 Player 或者 Cooker。(当然,这只是为了演示,在实际的项目中,一个类是不应该依赖于它的子类的。但如果这里的 getInstance () 方法位于其他的类中,就更具有的实际操作意义了)

2.4 第四个优势,在创建带泛型的实例时,能使代码变得简洁

这条主要是针对带泛型类的繁琐声明而说的,需要重复书写两次泛型参数:

Map<String,Date> map = new HashMap<String,Date>();

不过自从 java7 开始,这种方式已经被优化过了 —— 对于一个已知类型的变量进行赋值时,由于泛型参数是可以被推导出,所以可以在创建实例时省略掉泛型参数。

Map<String,Date> map = new HashMap<>();

所以这个问题实际上已经不存在了。

角色

工厂类角色---Factory

  • 工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑
  • 工厂类可以被外界直接调用,创建所需的产品对象

抽象产品角色---Product

它是工厂类所创建的所有对象的父类,所创建的具体产品对象都是其子类对象

具体产品角色---Concrete Product

工厂方法模式所创建的任何对象都是这个角色的实例

简单工厂模式UML图

适用场合

  1. 需要创建的对象较少,对象少不会造成工厂方法中的业务逻辑太过复杂
  2. 客户端只知道传入工厂类的参数,对于如何创建对象并不关心

使用方式

  • 在工厂类中提供一个创建产品的工厂方法,该方法可以根据所传入的参数不同创建不同的具体产品对象
  • 客户端只需调用工厂类的工厂方法并传入相应的参数即可得到一个产品对象,而无须直接使用new关键字来创建对象。

一个例子

结构
  • 产品接口类:Product.java
public interface Product {
}
  • 具体的产品类:Iphone.java
public class Iphone implements Product {
    public Iphone() {
        System.out.println("Iphone被制造了");
    }
}
  • 具体的产品类:Ipad.java
public class Ipad implements Product {
    public Ipad() {
        System.out.println("Ipad被制造了");
    }
}
  • 具体的产品类:Iwatch.java
public class Iwatch implements Product {
    public Iwatch() {
        System.out.println("Iwatch被制造了");
    }
}
  • 生产产品的工厂类 (简单的工厂类/静态工厂类):ProductFactory.java
public class ProductFactory {
    public static Product produce(String productName) {
        if(productName == null){
            return null;
        }
        switch (productName) {
        case "Iphone":
            return new Iphone();
        case "Ipad":
            return new Ipad();
        case "Iwatch":
            return new Iwatch();
        default:
            System.out.println("没有该类产品");
        return null;
        }
    }
}
  • 客户端调用工厂类静态方法创建产品:Client.java
public class Client {
    public static void main(String[] args) {
        ProductFactory.produce("Iphone");
        ProductFactory.produce("Ipad");
        ProductFactory.produce("Iwatch");
        ProductFactory.produce("Ipod");
    }
}
查看结果

这样的实现有个问题,如果我们新增产品类的话,就需要修改工厂类中的produce()方法,这很明显不符合 开放-封闭原则 。

简单工厂模式的缺点如下:

(1)由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
(2)使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度。
(3)系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
(4)简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

工厂方法模式

定义

工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。这样使得结构变得灵活起来——当有新的产品产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有的代码。可以看出工厂角色的结构也是符合开闭原则的

工厂方法模式的优缺点

优点

(1)在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无需关心创建细节,甚至无需知道具体产品类的类名。
(2)基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,正是因为所有的具体工厂类都具有同一抽象父类。
(3)使用工厂方法模式的另一个优点是在系统中加入新产品时,无需修改抽象工厂和抽象产品提供的接口,无需修改客户端,也无需修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。

缺点

(1)在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
(2)由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

角色

抽象工厂角色--- Factory

这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类

具体工厂角色--- Concrete Factory

它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象

抽象产品角色---Product

它是具体产品继承的父类或者是实现的接口

具体产品角色---ConcreteProduct

具体工厂角色所创建的对象就是此角色的实例

工厂方法模式UML图

适用场合

  • 一个类不知道它所需要的对象的类:
    在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类
  • 一个类通过其子类来指定创建哪个对象:
    在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏
  • 可配置
    将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中

一个例子

结构
  • 产品接口类:Product.java
public interface Product {
}
  • 具体的产品类:Iphone.java
public class Iphone implements Product {
    public Iphone() {
        System.out.println("Iphone被制造了");
    }
}
  • 具体的产品类:Ipad.java
public class Ipad implements Product {
    public Ipad() {
        System.out.println("Ipad被制造了");
    }
}
  • 抽象工厂:Factory.java
public interface Factory {
    public Product produce();
}
  • 具体工厂类:IphoneFactory.java
public class IphoneFactory implements Factory {
    @Override
    public Product produce() {
        return new Iphone();
    }
}
  • 具体工厂类:IpadFactory.java
public class IpadFactory implements Factory {
    @Override
    public Product produce() {
        return new Ipad();
    }
}
  • 客户端调用工厂创建产品:Client.java
public class Client {
    public static void main(String[] args) {
        Factory f1 = new IphoneFactory();
        f1.produce();
        Factory f2 = new IpadFactory();
        f2.produce();
    }
}
查看结果

参考文章
创建对象与使用对象——谈谈工厂的作用
深入理解工厂模式
JAVA设计模式之工厂模式(简单工厂模式+工厂方法模式)
关于 Java 的静态工厂方法,看这一篇就够了!
JAVA设计模式之 简单工厂模式【Simple Factory Pattern】

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。通常我们所说的工厂模式是指工厂方法模...
    zfylin阅读 1,270评论 0 7
  • 一、工厂模式介绍 工厂模式专门负责将大量有共同接口的类实例化。工厂模式可以动态决定将哪一个类实例化,不必事先知道每...
    端木轩阅读 12,507评论 1 20
  • 摘录 设计模式一 工厂模式Factory 在面向对象编程中, 最通常的方法是一个new操作符产生一个对象实例,ne...
    西江月阅读 432评论 0 2
  • 大家来欧洲一般都是看那些伫立了几百年的城堡和教堂,其实很多现当代建筑也是相当值得一看的。 作为一个建筑及设计迷,在...
    efb360938f79阅读 3,732评论 0 4
  • 我一直以为我的几个朋友是可以的,当我需要帮助的时候,她们能够很快给予我反应。但是这两天发生的事情让我觉得好像我错了...
    琅琊煎饼阅读 1,188评论 0 1