工厂模式(简单工厂、静态工厂、抽象工厂)
工厂模式提供了一种创建对象的方式,而无需指定要创建的具体类。
通过使用工厂模式,可以将对象的创建逻辑封装在一个工厂类中,而不是在客户端代码中直接实例化对象,这样可以提高代码的可维护性和可扩展性。
工厂模式的类型与简介:
- 简单工厂模式(Simple Factory):它使用一个单独的工厂类来创建不同的对象,根据传入的参数决定创建哪种类型的对象。
- 简单工厂模式不是一个正式的设计模式,但它是工厂模式的基础。
- 它属于创建型模式,但不属于 GoF 23 设计模式。
- 不符合“开放—封闭原则”,简单工厂中每一次扩展都需要对工厂类进行修改
- 静态工厂模式:将工厂类中的创建对象的功能定义为静态的,就是静态工厂模式。
- 它同样不是一种设计模式。
- 有时候,此模式归类为简单工厂模式的另一种实现方式。
- 工厂方法模式(Factory Method Pattern),又称多态性工厂模式:工厂方法模式定义了一个创建对象的接口,但由子类决定实例化哪个类。工厂方法将对象的创建延迟到子类。
- 针对接口编程,不针对实现编程,代码更具弹性,扩展性更好
- 符合开放 - 封闭原则:客户端决定实例化哪一个工厂来创建类,在扩展时不需要修改工厂类
- 解耦合:把创建对象的具体实现移到具体的工厂子类,在工厂接口类并不需要知道实际创建的对象是哪一个类,解耦了客户端中关于超类的代码和子类对象创建代码。
- 抽象工厂模式(Abstract Factory Pattern):抽象工厂模式提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。
关于以上分类方法的一些提醒:
- 在 GoF 的《设计模式》一书中,作者将简单工厂模式看作是工厂方法模式的一种特例。
- 《图解设计模式》一书中沿用 GoF《设计模式》中的分类方法。书中关于工厂模式介绍的是 Factory Method 模式和 Abstract Factory 模式。
- 有些观点认为上面的简单工厂模式(Simple Factory)和静态工厂模式是「简单工厂模式」的两种实现。
为了内容编排清晰,已将案例和理论分节处理。
一个简易计算器的案例
我们先从案例讲起。
通过代码实现一个简单计算器,具有加减乘除的运算功能:
1 | public class OperationFactory { |
如果我们不使用简单工厂模式,我们的代码会是这样的:
1 | public static void main(String[] args) { |
也就是说,简单工厂模式将一个复杂的类的创建过程,抽象为一个工厂类去做这个事情。
大部分工厂类都是以「Factory」这个单词结尾的,但也不是必须的,比如 Java 中的 DateFormat
、Calender
。除此之外,工厂类中创建对象的方法一般都是 create
开头,比如代码中的 createOperation()
,但有的也命名为 getInstance()
、createInstance()
、newInstance()
,有的甚至命名为 valueOf()
(比如 Java String
类的 valueOf()
函数)等等。
在上面的代码中,我们可以发现每次调用 createOperation()
函数都会 new
一个新的实例。如果这个实例可以复用,我们可以将它缓存起来,这就是后面使用到的静态工厂模式。
针对上一小节简单工厂模式的 OperationFactory
,我们可以将其改造为静态工厂模式:
1 | public class OperationFactory { |
在上面的代码中如果我们需要新增操作运算,就必须改动 OperationFactory
的代码,似乎有点违反开闭原则。如果「新增操作运算」这件事情很少发生,上面的代码也是可以接受的。
在这个模式中,我们把 OperationAdd
、OperationSub
这些看成是工厂方法模式中的「具体产品」角色;其抽象类 Operation
即为「产品」角色。每个具体的产品,都有各自对应的具体工厂而创建;各自的具体工厂,都继承着工厂接口:
1 | // 相较于简单工厂模式 OperationFactory 并不直接创建具体产品 |
当需要生成的产品不多且不会增加,一个具体工厂类就可以完成任务时,可删除抽象工厂类。这时工厂方法模式将退化到简单工厂模式。
各种「工厂模式」
工厂模式 | 开闭原则 | GoF 23 设计模式 | 简记 |
---|---|---|---|
简单工厂模式 | ❌ | ❌ | 一个具体工厂 |
静态工厂模式 | ✅ | 带缓存的具体工厂 | |
工厂方法模式 | ✅创建型 | 抽象工厂生产抽象产品 | |
抽象工厂模式 | 🟡 | 超级工厂 |
简单工厂模式
简单工厂「模式」是对于工厂最基础的应用,但它其实不能算作「工厂模式」,它不是一个设计模式,像是一种编程习惯。
应用层传参数给工厂就行,不需要关心如何创建。但是工厂类的职责相对过重,增加新的产品,需要修改工厂类的判断逻辑,违背了开闭原则。
静态工厂模式
创建类的实例的最常见的方式是用 new
语句调用类的构造方法。在这种情况下,程序可以创建类的任意多个实例,每执行一条 new
语句,都会导致 Java 虚拟机的堆区中产生一个新的对象。假如类需要进一步封装创建自身实例的细节,并且控制自身实例的数目,那么可以提供静态工厂方法。
静态工厂方法的特点:
- 不需要使用创建对象的方法实例化对象
- 不能通过继承来改变创建方法的行为
代码上的特点就是类的构造函数使用 private
修饰。
在有些情况下,一个类可以同时提供
public
的构造方法和静态工厂方法。用户可按需使用获取类的方式。
例子:
Class.forName
Integer.valueOf
Foo.getInstance
静态工厂方法与用 new
语句调用构造方法的区别:
静态工厂模式 | new 语句 |
|
---|---|---|
构造方法名字 | 方法名任意,提高代码可读性。但和其他的静态方法没有明显区别,我们可以通过约定命名的方式使用户区分一些静态方法专门负责返回类的示例,比如 valueOf 、getInstance 。 |
必须与类名相同。但不能从名字上区分每个被重载的构造方法。 |
调用时生成的对象数 | 每次调用创建对象数目取决于方法的具体实现。 | 每次调用都会生成一个新的对象。 |
能否返回子类的实例 | 可以。这一特性可以实现松耦合的系统接口。具体详看:工厂(方法)模式 Factory 章节 | 不能,只能返回当前类的实例。 |
静态工厂方法最主要的特点是:每次被调用的时候,不一定要创建一个新的对象。利用这一特点,静态工厂方法可用来创建以下类的实例:
- 站内文章单例(Singleton)类:只有唯一的实例的类。详看:饿汉模式。
- 枚举类:实例的数量有限的类。自定义枚举类而不是简单的定义 0,1 之类的数字在提高代码可读性的同时,还可以引入 Java 编译器的检查。JDK5 开始,有专门的
java.lang.Enum
枚举类型的语法。 - 具有实例缓存的类:能把已经创建的实例暂且存放在缓存中的类。
- 具有实例缓存的不可变类:不可变类的实例一旦创建,其属性值就不会被改变。
静态工厂方法的优缺点
Consider static factory methods instead of constructors.
创建对象时尽量考虑静态工厂方法,从而你可能见到以下场景:
1 | List<String> list = Lists.newArrayList(); // 明明可以写成list = new ArrayList() |
优点:
- 静态工厂方法可以通过方法名字来表示创建了什么对象
- 创建对象时不需要每次都创建一个新的。这也叫做实例控制(instance-controlled),比如
Boolean
的valueOf
方法。 - 静态工厂方法可以返回该类的子类。如果
new
的话就只能返回某个类,但是静态工厂方法可以返回其子类。比如有个静态对象的返回值是Set
,那么方法返回HashSet
。 - 可以返回
private class
的对象。 - 静态工厂方法可以根据参数来调整返回的子类
- 静态工厂方法创建的对象可以暂时不存在。典型的例子就是通过反射来创建对象,给一个对象的全路径为参数(比如
java.uril.ArrayList
)然后通过反射的方式创建之后返回。那么在这种情况下new
肯定是不行了(类都没有根本没办法new
)。
缺点:由于没有 public
的构造函数,那么这个类就没办法被继承,也就没法重写其方法。缺点中的优点就是鼓励程序员用组合而不是继承。
工厂方法模式 Factory Method
将实例的生成交给子类
如果我们将 Template Method 模式用于生成实例,他就演变为 Factory Method 模式。
在 Template Method 模式中,父类规定处理的流程,子类实现具体的处理;在 Factory Method 模式中,父类决定实例的生成方式,但并不决定所要生成的具体的类,具体处理全部交给子类负责。这样可以将生成实例的框架和实际负责生成实例的类解耦。
在工厂模式中,核心的工厂类不再负责所有的产品的创建,而是将具体的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品应当被实例化这种细节。
抽象类 Creator
中的 create
方法使用 final
关键字修饰,表明不应该重写模板方法。
例子:
1 | package framework; |
登场角色:
Product
(产品):框架,抽象类,定义该模式下生成实例所持有的接口。具体的处理由ConcreteProduct
角色决定。Creator
(创建者):框架,负责生成Product
角色的抽象类。不用new
关键字来生成实例,而是调用生成实例的专用方法来生成实例,这样就可以防止父类与其他具体类耦合。ConcreteProduct
(具体的产品):加工方,决定具体的产品。ConcreteCreator
(具体的创建者):加工方,负责生成具体的产品。
拓展思路:
- 如果要创建其他产品,直接引入
framework
(框架)包。 - 生成实例的方法,
Creator
里的create
方法,可以有三种实现方式:
1 | // 方式1:在Creator里指定create为抽象方法 |
工厂方法模式非常符合“开闭原则”,当需要增加一个新产品时,我们只需要增加一个具体的产品类和与之对应的具体工厂即可,无须关系产品的创建过程,甚至连具体的产品类名称都不需要知道。虽然他很好的符合了“开闭原则”,但是由于每新增一个新产品时就需要增加两个类,这样势必就会导致系统的复杂度增加。
工厂方法模式和简单工厂模式的区别:
- 简单工厂把有关实例化的全部事情,都在工厂类中处理(没有抽象工厂类),然而工厂方法模式创建了一个框架,让工厂子类决定要如何实现。
- 克服了简单工厂违背“开放 - 封闭原则”的缺点,又保持了封装对象创建过程的优点,对简单工厂的进一步抽象和推广
相关的设计模式:
- 站内文章单例模式 Singleton:在多数情况下我们都可以将 Singleton 模式用于扮演 Creator 角色(或是 ConcreteCreator 角色)的类。这是因为在程序中没有必要存在多个 Creator 角色(或是 ConcreteCreator 角色)的实例。不过在示例程序中,我们并没有使用 Singleton 模式。
- Composite 模式 (第 11 章):有时可以将 Composite 模式用于 Product 角色(或是 ConcreteProduct 角色)。
- 模板方法模式 Template Method:Factory Method 模式是 Template Method 的典型应用。在示例程序中,create 方法就是模板方法。
- 迭代器模式 Iterator:有时,在 Iterator 模式中使用 iterator 方法生成 Iterator 的实例时会使用 Factory Method 模式。
抽象工厂 Abstract Factory
将关联零件组装成产品。
抽象工厂模式(Abstract Factory Pattern)的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。抽象工厂的工作是将“抽象零件”组装为“抽象产品”。
抽象:不考虑具体实现,而是仅关注接口(API)的状态。
依赖倒置原则:要依赖抽象,不要依赖具体的类。
登场角色:
- AbstractProduct(抽象产品):定义 AbstractFactory 角色所生成的抽象零件和产品接口(API)。
- AbstractFactory(抽象工厂):定义用于生成抽象产品的接口(API)。
- Client(委托者):调用 AbstractFactory 角色和 AbstractProduct 角色的接口(API)来进行工作,对于具体的零件、产品和工厂一无所知。比如示例程序中的 Main,根据 Factory 接口构造一篇 HTML。
- ConcreteProduct(具体产品):负责实现 AbstractProduct 角色的接口(API)。
- ConcreteFactory(具体工厂):负责实现 AbstractFactory 角色的接口。
使用抽象工厂模式一般要满足以下条件:
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
抽象工厂模式和工厂方法模式的异同:
- 创建对象时:
- 工厂方法模式:继承。通过工厂子类创建对象,客户端只需要知道具体的抽象类型,由工厂子类决定具体类型,只负责将客户从具体类型中解耦。
- 抽象工厂模式:对象的组合。提供用来创建一个产品家族的抽象类,这个类型的子类定义产品被创建的方法,使用时通过实例化具体工厂类,并将工厂类传入针对抽象类型写的代码中,把客户从所使用的实际具体产品中解耦。
- 抽象工厂集合了一群相关的产品类,而工厂方法只需要创建一个产品。抽象工厂中的每一个方法创建一个具体类,实际是利用工厂方法实现。
优缺点:
- 优点:具体产品在应用层代码隔离,无须关心创建细节;将一个系列的产品族统一到一起创建。
- 缺点:规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口;增加了系统的抽象性和理解难度。
抽象工厂模式的扩展有一定的“开闭原则”倾斜性:
- 当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则。
- 当产品族中需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则。
另一方面,当系统中只存在一个等级结构的产品时,抽象工厂模式将退化到工厂方法模式。
相关设计模式:
- 工厂(方法)模式 Factory:有时 Abstract Factory 模式中零件和产品的生成会使用到 Factory Method 模式。
- Composite 模式(第 11 章):有时 Abstract Factory 模式在制作产品时会使用 Composite 模式。
- 站内文章单例模式 Singleton:有时 Abstract Factory 模式中的具体工厂会使用 Singleton 模式。
其他内容
Java 生成实例的方式
在介绍工厂模式之前,先介绍一下 Java 生成实例有哪些方式。
通过 new
的方法方式可以生成实例,这种方法类名会出现在代码中,形成强耦合关系。
1 | // new关键字 |
我们可以通过 clone
方法,根据现有实例复制出一个新实例。这个过程中不会调用构造函数。
1 | // clone方法 |
我们还可以通过 newInstance
的方法生成实例:
1 | // java.lang.Class类的newInstance方法。通过Class类的实例生成出Class类所表示的类(即Something类)的实例(会调用无参构造函数)。 |
class.loadClass
方法:
1 | ClassLoader cl; |
反序列化方式。即通过 I/O 流(包括反序列化),如运用反序列化手段,调用 java.io.ObjectInputStream
对象的 readObject()
方法。
结尾
1995 年,Gang of Four(四人组)合作出版了《Design Patterns: Elements of Reusable Object-Oriented Software》一书,共收录了 23 种设计模式,从此树立了软件设计模式领域的里程碑,人称「GoF 设计模式」。
后续计划
- 补充「抽象工厂模式」的案例学习
- 语言风格需要统一、逻辑需要清晰
本文 PlantUML 归档
工厂方法模式:
1 | package frame{ |
抽象工厂模式:
1 | package factory{ |
本文参考
- 设计模式辨析——工厂篇(简单工厂、静态工厂、工厂方法、抽象工厂) - 知乎
- 还在new对象吗?试试静态工厂方法–Effective Java第二章 - 知乎
- 《图解设计模式》
- 工厂模式 | 菜鸟教程 (runoob.com)
- 本科生课程笔记《程序设计中级实践&设计模式》 - TJU 🍐⚱️
- 极客时间专栏 - 设计模式之美 - 王争
- 设计模式之工厂模式(3种)详解及代码示例 - kosamino - 博客园
- 实例化类的五种方法-CSDN博客
- 万字详解 GoF 23 种设计模式(多图、思维导图、模式对比),让你一文全面理解_gof23-CSDN博客