【设计模式】工厂模式
0. 工厂模式简介
【工厂模式】属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。这样可以提高代码的可维护性和可扩展性。
工厂模式定义:定义了一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的 “创建与使用相分离” 的特点。
PS:工厂模式适用于生成复杂对象的场景。如果对象较为简单,通过 new 即可完成创建,则不必使用工厂模式。使用工厂模式会引入一个工厂类,增加系统复杂度。
工厂模式有三种,分别是简单工厂模式(Simple Factory Pattern)、工厂方法模式(Factory Method Pattern)和抽象工程模式(Abstract Factory Pattern),本文将对其逐一进行介绍。
1. 简单工厂模式
【简单工厂模式】,又叫做静态工厂模式(Static Factory Method),由一个工厂对象决定创建出哪一种产品类的实例,简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。简单工厂模式属于创建型模式,但不属于GoF 23 种设计模式。
1.1 优缺点及应用场景
优点
- 封装性好:客户端不需要知道对象创建的细节,只需知道具体的工厂类和方法,降低了客户端与具体产品类的耦合度。
- 易于扩展:当需要添加新的产品时,只需要修改工厂类中的创建逻辑,而不需要修改客户端代码。
- 代码复用:如果产品之间的创建逻辑相似,可以复用工厂类中的代码,提高代码复用率。
- 隐藏了产品类的实现细节:客户端只需要知道产品的抽象接口,而不需要知道具体产品类的实现细节。
缺点
- 不符合开闭原则:如果要增加新的产品,就需要修改工厂类的代码,这违反了开闭原则(对扩展开放,对修改关闭)。
- 工厂类职责过重:随着产品种类的增加,工厂类的代码会越来越复杂,这会增加代码的维护难度。
- 不易于实现产品族的扩展:简单工厂模式通常只用于创建同一类型的产品,如果要创建不同类型的产品(如产品族),就需要使用其他工厂模式,如【抽象工厂模式】。
应用场景
- 创建对象较少且不经常改变时:当系统中只需要几种对象时,使用简单工厂模式可以很好地封装对象的创建过程,提高代码的复用性和可维护性。
- 客户端不关心对象的创建细节:当客户端只需要使用产品的抽象接口,而不需要知道具体产品类的实现细节时,可以使用简单工厂模式来隐藏对象的创建过程。
- 工厂类集中管理对象创建:当系统中存在多个产品类,且这些产品类的创建逻辑相似时,可以使用简单工厂模式来集中管理这些对象的创建过程,减少代码的冗余。
- 需要根据条件动态选择具体产品:在某些情况下,客户端需要根据某些条件动态地选择创建哪种具体产品。此时,可以使用简单工厂模式来封装这些条件判断逻辑,使得客户端代码更加简洁和清晰。
1.2 模式的结构
【简单工厂模式】包含以下几个主要角色:
简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
具体产品(ConcreteProduct):是简单工厂模式的创建目标。
【简单工厂模式】结构图:
1.3 代码实现
首先需要一个抽象产品(一个接口):
1 | public interface Product { |
让具体的产品实现抽象产品:
1 | public class ConcreteProduct1 implements Product { |
1 | public class ConcreteProduct2 implements Product { |
然后再编写一个简单工厂,根据不同的产品编号,生产出不同的产品:
1 | public final class SimpleFactory { |
测试:
1 |
|
测试结果:
1 | 我是具体产品 1 号... |
2. 工厂方法模式
【简单工厂模式】违背了开闭原则,而【工厂方法模式】则是【简单工厂模式】的进一步深化,其不像【简单工厂模式】通过一个工厂来完成所有对象的创建,而是通过不同的工厂来创建不同的对象,每个对象由对应的工厂创建。即工厂方法模式在添加新产品时,只需要增加相应的具体产品类和具体工厂类,而不需要修改原有的代码,符合开闭原则。
定义:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。
核心思想:将对象的实例化操作延迟到子类中进行,通过定义一个创建对象的接口,由子类来决定实例化哪一个类。
2.1 优缺点及应用场景
优点
- 扩展性好:当需要添加新的产品时,只需要新增一个具体的产品类和对应的工厂类,而无需修改已有的代码,符合“开闭原则”。系统中类的个数虽然会成对增加(产品类和工厂类),但系统整体的扩展性良好。
- 降低耦合度:客户端只需要知道所使用产品的抽象接口和对应的工厂接口,而无需知道具体的产品类和工厂类的实现细节,降低了客户端与具体产品类之间的耦合度。
- 封装性好:将对象的创建过程封装在工厂类中,客户端通过工厂接口获取产品对象,而无需关心对象的创建细节。
- 多态性的应用:客户端通过调用工厂方法来创建对象,而无需关心具体的产品类,这样可以在不修改客户端代码的情况下,通过替换具体工厂类来创建不同的产品对象。
缺点
- 类的个数增加:每增加一个新的产品,就需要增加一个具体的产品类和一个对应的工厂类,这在一定程度上增加了系统的复杂度。
- 引入抽象层:考虑到系统的可扩展性,需要引入抽象层,这可能会增加系统的抽象性和理解难度;在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
- 可能增加系统开销:由于系统中类的个数增加,可能需要更多的类进行编译和运行,这可能会给系统带来一些额外的开销。
应用场景
- 当一个类不知道它所必须创建的对象的类的时候:在工厂方法模式中,客户端只需要知道它需要什么样的产品,而不需要知道如何创建产品。
- 当一个类希望其子类决定在运行时创建的对象时:工厂方法允许子类决定实例化哪个类,这样就可以在运行时根据需要动态选择。
- 当系统需要产品对象的家族,但是产品的创建逻辑不固定时:通过工厂方法模式,可以在不修改已有代码的情况下,添加新的产品类和对应的工厂类。
- 当系统需要依赖抽象而不是具体类时:工厂方法模式通过使用抽象工厂和抽象产品,使得客户端与具体的产品类解耦,依赖于抽象。
2.2 模式的结构
【工厂方法模式】包含以下几个主要角色:
抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法
newProduct()
来创建产品。具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
【工厂方法模式】结构图:
2.3 代码实现
前面提到【工厂方法模式】是【简单工厂模式】的进一步深化,那我们就在【简单工厂模式】的代码实现上进一步完善。
首先是引入抽象工厂(一个接口):
1 | public interface AbstractFactory { |
然后针对不同的产品使用不同的工厂进行实现,我们前面有两件产品,因此需要两个具体的工厂(具体的工厂实现抽象工厂):
1 | public class ConcreteFactory1 implements AbstractFactory { |
1 | public class ConcreteFactory2 implements AbstractFactory { |
测试:
1 |
|
测试结果:
1 | 我是具体产品 1 号... |
3. 抽象工厂模式
在上面使用工厂方法模式时会发现:一个抽象工厂类可以派生出多个具体工厂类,但每个具体工厂类只能创建一个具体产品类的实例,这就导致了如果产品的种类激增,那么也会使得类的数量激增。
为了解决这个问题,我们可以使用【抽象工厂模式】。
抽象工厂模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
可以简单理解为套娃,就是使用一个超级工厂创建其他二级工厂。在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂方法模式提供对象。
3.1 产品等级与产品族
首先我们需要弄明白两个概念:产品等级、产品族。
如图:
从这张图中可以看到:同一个牌子属于同一个产品族,同一个类型属于同一个产品等级。
那这两个概念有啥用呢?
【工厂方法模式】只考虑生产同等级的产品,而【抽象工厂模式】将考虑生产多等级的产品。
也就是说,【抽象工厂模式】是【工厂方法模式】的升级版。
但也并不是说直接上【抽象工厂模式】就完事了,使用【抽象工厂模式】一般需要满足以下条件: 系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
3.2 优缺点及应用场景
优点
- 封装性:抽象工厂模式隐藏了具体产品的创建细节,客户端只需要关心产品的抽象接口,无需知道具体的实现细节。
- 产品族支持:抽象工厂模式能够很方便地支持产品族(即一组相互关联或相互依赖的产品)的概念。客户端可以从同一个工厂中获取多个相关的产品对象,而不需要知道具体对象的类名。
- 扩展性:如果需要增加新的产品族,只需要增加一个新的工厂类即可,原有系统代码无需修改,符合开闭原则。
- 高内聚低耦合:将对象的创建与使用过程分离,降低了系统的耦合度,提高了系统的可维护性和可扩展性。
缺点
- 系统复杂度增加:由于引入了抽象工厂层、抽象产品层、具体工厂层、具体产品层等多层结构,使得系统的复杂度增加。
- 产品族扩展困难:如果需要向产品族中添加新的产品,则可能需要修改抽象工厂接口及其所有实现类,这在一定程度上增加了系统的维护成本。
- 难以支持新种类的产品:抽象工厂模式是基于产品族来设计的,如果系统需要支持新的产品种类(即不属于任何现有产品族的产品),则需要对现有系统进行较大的修改,甚至可能需要引入新的抽象工厂接口和实现类。
应用场景
- 当系统需要创建的对象存在多个产品族,并且系统一次只可能消费产品族中的某一族产品:例如,一个系统需要支持多种数据库访问方式(如MySQL、Oracle等),每种数据库访问方式都需要提供多种数据访问对象(如Connection、Statement等)。此时,可以使用抽象工厂模式来创建不同数据库访问方式下的数据访问对象。
- 系统的产品有多于一个的产品族,而系统只消费其中某一族的产品:在这种情况下,抽象工厂模式允许客户端在不知道具体实现的情况下,选择并使用一组产品。
- 同一家族中多个对象被设计在一起工作时:在这种情况下,客户端只需要关心产品的抽象接口,而不需要关心产品的具体实现和它们之间的依赖关系。抽象工厂模式可以确保客户端始终使用同一产品族中的对象,从而保证了产品之间的兼容性。
- 提供一个产品类的库,并且只显示他们的接口而不是实现:这种情况下,抽象工厂模式隐藏了产品的具体实现细节,只向客户端暴露产品的接口。
3.3 模式的结构
【抽象工厂模式】包含以下几个主要角色:
抽象工厂(Abstract Factory):声明了一组用于创建产品对象的方法,每个方法对应一种产品类型。抽象工厂可以是接口或抽象类。
具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
【抽象工厂模式】结构图:
3.4 代码实现
根据上述的结构图,很容易完成代码的实现。首先需要两个抽象产品(表示两种产品等级、两种类型的产品):
1 | public interface Product1 { |
1 | public interface Product2 { |
然后针对每种抽象产品又有不同的实现,比如 Product1
有两种实现:
1 | public class ConcreteProduct11 implements Product1 { |
1 | public class ConcreteProduct12 implements Product1 { |
Product2
也有两种实现:
1 | public class ConcreteProduct21 implements Product2 { |
1 | public class ConcreteProduct22 implements Product2 { |
然后我们需要一个抽象工厂,这个工厂可以生成不同产品等级的产品,也就是说可以生产处于同一个产品族(同一个牌子下的产品):
1 | public interface AbstractFactory { |
针对上述四件产品,需要两个工厂,每个工厂生产两种类型的产品,这两种产品属于同一个产品族(同一个品牌):
1 | public class ConcreteFactory1 implements AbstractFactory { |
1 | public class ConcreteFactory2 implements AbstractFactory { |
测试:
1 |
|
测试结果:
1 | 我是 Product1 类型的产品 1... |