前言
关于设计模式,是一个永远说不完的也说不清的话题。毕竟在编程的世界里,没有最好的设计模式,只有最合适的设计模式。甚至有些时候,程序或者问题不到一定的规模,尝试所有的设计模式都是花架子。另外,在程序设计之初就谈论设计模式有些为时过早,但在问题出现之后才想起来设计模式却有为时已晚,毕竟后期代码的重构或者逻辑的优化都不是一件轻轻松松就能完成的事情。所以,在合适的地方在合适的时机使用合适的设计模式,恰好能体现出来一个开发者的优秀程度。
设计模式就像是武功的套路,每一个套路都有固定的招式。而每一个套路也不是万能的,不同的套路解决不同的问题。初学武功的人,只能以模仿的方式一招一式的练习,而大师级别的武术宗师心中却不受这些套路的桎梏。长时间的习武,反反复复的练习,早就把这些套路深深的印在了骨子里。必要的时候,就能不经思考的下意识出招。同理,深刻理解并经常应用设计模式的开发者,遇到问题的时候,可以熟练的筛选出来合适的设计模式。甚至有些时候,他们还可以把这些设计模式进行组合或者进行一些改造,来达到更好的效果,无招胜有招,心中无模式却胜过有模式,这大概就是设计模式的最高境界。
本篇文章要说的设计模式包括:简单工厂模式、工厂模式、抽象工厂模式。之所以把他们放到一起讲,因为这三个设计模式都属于创建型模式,即这三个设计模式都是为了解决对象的创建而生。把这三个设计模式拿来一起讲,可以更好的体现出来各自的优缺点。
(一)简单工厂模式
简单工厂模式又叫做静态工厂方法,属于创建型模式。前面已经说过,简单工厂模式解决的是对象的创建问题,简单工厂模式主要由三个角色构成:工厂类、抽象产品类、具体产品类。
1.1 简单工厂模式定义
定义一个用于生产对象的类,封装生产不同的产品实例的细节,使创建对象的逻辑和客户端分离,客户端只需向这个类发起请求即可获得对应的产品实例,而无需关心对象的创建过程。
简单工厂模式解决的问题:客户端(此处的客户端指的是程序中使用产品类的某个类)根据不同的业务场景创建不同的产品对象的问题,而无需关心产品的的创建细节。
使用了简单工厂模式,客户端不需要增加创建产品类的代码,而创建产品类的代码被转移到了工厂类中,客户端通过调用工厂类的某个接口并且给这个接口传入对应的参数,就可以获得客户端需要的产品类的对象。这样的好处在于产品类的创建逻辑和客户端实现类分离,客户端不需要关心产品类的生产过程,只需要消费这个产品。
1.2 简单工厂模式UML类图
1.3 实例代码
/// 简单工厂类
enum ProductEnum {
case ProductA
case ProductB
}
class SampleFactory: NSObject {
/// 注意这里是类方法,即静态方法,这就是静态工厂方法一名的由来
public class func product(product : ProductEnum) -> AbstractProduct {
switch product {
case .ProductA:
return ConcreteProductA()
case .ProductB:
return ConcreteProductB()
}
}
}
/// 抽象产品类:
class AbstractProduct: NSObject {
func interface() {
}
}
/// 具体产品类A
/// 继承自抽象产品类,覆写抽象产品类的相应接口
class ConcreteProductA: AbstractProduct {
override func interface() {
print("简单工厂模式:调用产品A实例方法")
}
}
/// 具体产品类B
/// 继承自抽象产品类,覆写抽象产品类的相应接口
class ConcreteProductB: AbstractProduct {
override func interface() {
print("简单工厂模式:调用产品B实例方法")
}
}
/// 客户端调用:
let facotry: SampleFactory = SampleFactory()
let productA: AbstractProduct = facotry.product(product: .ProductA)
productA.interface()
let productB: AbstractProduct = Factory.product(.ProductB)
productB.interface()
1.4 优点
封装对象的创建过程,使创建对象的逻辑和客户端分离。 在简单工厂模式中,工厂类是整个模式的核心。在不使用简单工厂模式之前,如果我们要在客户端根据不同的场景创建不同的产品对象,我们就必须要写一些类似于if...else if...else...这样的条件分支语句。在使用了简单工厂模式后,原本属于客户端的逻辑判断代码,全部被转移到了工厂类内部。所以,工厂类代理了客户端的一部分逻辑判断功能。而客户端不再关心产品的具体创建细节,客户端只需要调用工厂类的指定接口,给这个接口传入不同的参数,工厂类就会根据客户端选择传递的参数动态实例化相关的类,然后给客户端返回一个实例化的对象。在这个模式中,工厂类负责“生产”对象,而客户端负责“消费”对象。工厂类和客户端明确了各自的职责和权利。
1.5 缺点
不符合开放-封闭原则。上面已经说过,在简单工厂模式中,把原本属于客户端的逻辑判断代码转移到了工厂类中,所以当需要增加或者删除某个产品类的时候,都需去工厂类中进行修改。这违反了编程中的开放封闭原则,即对扩展开放,对修改封闭。如果不明白:每增加一个产品类,都要去工厂类中增加对应的条件分支代码,这就是对修改开放了,所以违背了开放封闭原则。
1.6 注意
一定要使用静态方法创建产品实例。工厂类中创建并返回产品实例的方法是类方法,即静态方法,这也是该模式的静态工厂方法别名的由来。所以,不要把这个方法写成对象方法。
1.7 使用场景
1.客户端不需要关心或者不需要参与具体产品类的创建细节。
2.工厂类中创建的产品对象不是很多。
3.因为工厂类违背了开放-封闭原则,所以在工厂类不需要频繁改动、产品类不常变化的地方可以考虑使用该模式。
(二)工厂方法模式
工厂方法模式经常被叫做工厂方法,和简单工厂模式一样,也是属于创建型模式。工厂方法模式由抽象工厂类、具体工厂类、抽象产品类、具体产品类4个类构成。
2.1 工厂方法定义
定义一个用于创建对象的接口,让子类决定实例化哪一个产品类,工厂方法使一个类的实例化延迟到其子类。
2.2 工厂方法UML类图
2.3 实例代码
/// 抽象工厂类
// MARK:- 工厂类要实现的接口
protocol IFactory {
// 工厂类实现该接口用于生产产品实例,抽象工厂类实现该接口;具体工厂类重写该接口,返回具体的产品实例
func Manufacture() -> AbstractProduct;
}
// MARK:- 抽象工厂类
class AbstractFactory: NSObject,IFactory {
func Manufacture() -> AbstractProduct {
return AbstractProduct()
}
}
/// 抽象产品类
//MARK: 产品类需要实现的接口,抽象产品类实现该方法,具体产品类覆写该方法
protocol IProduct {
func operation()
}
// 抽象产品类
class AbstractProduct: NSObject,IProduct {
func operation() {
print("工厂方法模式:调用了抽象产品实现的接口")
}
}
/// 具体工厂类
class ConcreteFactoryA: AbstractFactory {
override func Manufacture() -> AbstractProduct {
return ConcreteProductA()
}
}
class ConcreteFactoryB: AbstractFactory {
override func Manufacture() -> AbstractProduct {
return ConcreteProductB()
}
}
/// 具体产品类
class ConcreteProductA: AbstractProduct {
override func operation() {
print("工厂方法模式:调用了产品A实现的接口")
}
}
class ConcreteProductB: AbstractProduct {
override func operation() {
print("工厂方法模式:调用了产品B实现的接口")
}
}
/// 客户端调用
let factoryA : AbstractFactory = ConcreteFactoryA()
let productA : AbstractProduct = factoryA.Manufacture()
productA.operation()
let factoryB : AbstractFactory = ConcreteFactoryB()
let productB : AbstractProduct = factoryB.Manufacture()
productB.operation()
2.4 优点
工厂方法模式克服了简单工厂模式的缺点,即简单工厂模式违背的开放封闭原则。简单工厂模式的产品类和分支语句耦合,每增加一个产品类,就要去工厂类中增加相应的分支语句。而工厂方法模式抽象出来一个接口,这个接口就是创建抽象产品的工厂方法,这个接口由抽象工厂类实现,具体的工厂类进行覆写以生产不同的具体产品。以后我们再增加某个产品,只需要增加对应的具体工厂类和具体产品类。而无需修改原有的工厂类,这样就不会违背开放封闭原则。
工厂方法符合单一职责原则,即每一个具体的工厂类只负责生产一种具体的产品。
2.5 缺点
- 添加新的产品类时,还要添加对应的工厂类。这样,每增加一个产品类,都要创建两个类,有多少个具体的产品类,就要有多少个具体的工厂类。这就意味着,会有更多的类需要参与程序的编译。
- 一个具体的工厂类只能创建一类产品。
- 为了达到程序的扩展性,工厂方法模式引入了抽象类,在客户端代码中均使用抽象类进行定义实例,增加了程序的抽象性和理解的难度。且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
- 虽然保证了工厂方法内的对修改关闭,但对于使用工厂方法的类,如果要更换另外一种产品,仍然需要修改实例化的具体工厂类;
2.6 工厂方法 vs 简单工厂
- 相比较简单工厂模式,工厂方法没有使用静态方法创建产品,而是使用工厂的对象方法创建产品实例,即,客户端先创建具体的工厂实例,然后再使用这个工厂实例创建对应的产品实例。
- 工厂方法模式解决了简单工厂模式违背开放封闭原则的问题。
- 客户端实例化产品时,使用简单工厂模式,客户端不需要关心产品实例的创建过程。工厂方法模式把简单工厂模式的逻辑又搬到了客户端,使用工厂方法模式,客户端选择判断的逻辑还是存在的,客户端还是需要书写额外的逻辑来决定实例化哪一个工厂类,继而在让工厂对象创建对应的产品对象。
2.7 使用场景
- 当一个类不知道它所需要的对象的类时,在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可;
- 当一个类希望通过其子类来指定创建对象时,在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
(三)抽象工厂模式
和简单工厂、工厂方法一样,抽象工厂模式也是一种创建型模式。和简单工厂工厂方法相比,抽象工厂更具有普遍性,更具有抽象性。和工厂方法一样,抽象工厂模式也是又由抽象工厂类、具体工厂类、抽象产品类、具体产品类4个类构成。
3.1 抽象工厂模式定义
为创建一系列相关或相互依赖的对象提供一个接口,而且无需指定他们的具体的类。
3.2 抽象工厂模式UML类图
抽象工厂模式中有两个比较难以理解的概念,分别是产品树
(或产品系列)和产品簇
概念的。在抽象工厂模式中,有多少个抽象产品类,就有多少个产品树,因为抽象产品类和其衍生的若干个具体产品类在形式上组成了一个树状结构,所以称之为产品树。而不同的产品树中相互关联的产品组成了一个产品簇,一个具体的工厂类可以生产一个产品簇内的所有类型的产品实例。上面的UML中的ConcreteProductA1和ConcreteProductA2组成了一个产品簇,这个产品簇内的所有产品可以由ConcreteFacotory1来生产。同理,ConcreteProductA2和ConcreteProductB2组成了另一个产品簇,这个产品簇内的产品可以由ConcreteFactory2来生产。而抽象工厂模式是针对于产品簇这一概念的。因为每个具体工厂可以实例化多种产品实例。这些产品实例实际上是有一定关联的,或者说这些产品实例是有一定共同特点的。以下用使用苹果手机和诺基亚手机来举例说明产品树和产品簇的概念。
上图中,苹果手机是一个产品系列(或称产品树),诺基亚手机是另一个产品系列。而苹果手机和诺基亚手机系列下都有5英寸和6英寸的手机,那么5英寸的苹果手机和5英寸的诺基亚手机就构成了一个产品簇。这个产品簇的产品可以由5InchFactory这个工厂来生产。
3.3 实例代码
/// 抽象工厂类
protocol IFactory {
func createProductA() -> AbstractProductA
func createProductB() -> AbstractProductB
}
class AbstractFactory: NSObject, IFactory {
func createProductA() -> AbstractProductA {
return AbstractProductA()
}
func createProductB() -> AbstractProductB {
return AbstractProductB()
}
}
/// 抽象产品类A
protocol IProductA {
func operationA()
}
class AbstractProductA: NSObject, IProductA {
func operationA() {
}
}
/// 抽象产品类B
protocol IProductB {
func operationB()
}
class AbstractProductB: NSObject, IProductB {
func operationB() {
}
}
/// 具体工厂类1
class ConcreteFactory1: AbstractFactory {
func createProductA() -> AbstractProductA {
return ConcreteProductA1()
}
func createProductB() -> AbstractProductB {
return ConcreteProductB1()
}
}
/// 具体工厂类2
class ConcreteFactory2: AbstractFactory {
func createProductA() -> AbstractProductA {
return ConcreteProductA2()
}
func createProductB() -> AbstractProductB {
return ConcreteProductB2()
}
}
/// 具体产品类A1
class ConcreteProductA1: AbstractProductA {
override func operationA() {
print("调用了ConcreteProductA1的接口")
}
}
/// 具体产品类A2
class ConcreteProductA2: AbstractProductA {
func operationA() {
print("调用了ConcreteProductA2的接口")
}
}
/// 具体产品类B1
class ConcreteProductB1: AbstractProductB {
override func operationB() {
print("调用了ConcreteProductB1的接口")
}
}
/// 具体产品类B2
class ConcreteProductB2: AbstractProductB {
override func operationB() {
print("调用了ConcreteProductB2的接口")
}
}
/// 客户端调用
let factory1 = ConcreteFactory1()
let productA1 = factory1.createProductA()
let productB1 = factory1.createProductB()
productA1.operationA()
productB1.operationB()
let factory2 = ConcreteFactory2()
let productA2 = factory2.createProductA()
let productB2 = factory2.createProductB()
productA2.operationA()
productB2.operationB()
3.4 抽象工厂 vs 工厂方法
工厂方法模式的定义是:定义一个用于创建对象的接口,让子类决定实例化哪一个产品类,工厂方法使一个类的实例化延迟到其子类。抽象工厂的定义是:为创建一系列相关或相互依赖的对象提供一个接口,而且无需指定他们的具体的类。
从UML类图中,不难看出,抽象工厂模式和工厂方法模式是非常相似的,只不过抽象工厂模式比工厂方法模式多了一些抽象类和具体的产品类。这就是他们的区别所在。二者的区别在于:工厂方法的产品类单一,工厂方法有一个抽象工厂类和一个抽象产品类,有若干个具体工厂类和具体产品类,每一个具体的工厂类都对应一个具体产品类。每种具体工厂类只能实例化一种具体产品类。所以具体的工厂类和具体的产品类的个数是相等的。而抽象工厂模式是针对于解决多个系列的产品而诞生的。即在抽象工厂模式中,抽象产品不止有一种。如果我们把一种抽象产品比作成一个产品系列(或者比作一棵产品树),那么抽象工厂模式下会有多个系列(或多棵产品树)。每个产品系列下又有包括多种不同类型的产品。以下是工厂方法模式和抽象工厂的比对:
工厂方法模式 | 抽象工厂模式 |
---|---|
定义一个用于创建对象的接口,让子类决定实例化哪一个产品类,工厂方法使一个类的实例化延迟到其子类。 | 为创建一系列 相关或相互依赖的对象提供一个接口,而且无需指定他们的具体的类。 |
针对一个 产品树 |
针对多个 产品树 |
一个 抽象产品类 |
多个 抽象产品类 |
可以派生出多个具体产品类 | 每个抽象产品类可以派生出多个具体产品类 |
每个具体工厂类只能创建 一种 具体产品类的实例 |
每个具体工厂类可以创建 多种 具体产品类的实例(至于多少种,取决于有多少个产品树) |
3.5 优点
- 易于在不同的产品簇之间切换。要想切换不同的产品簇,只需要切换具体工厂。即初始化不同的具体工厂实例就可以达到不同产品类型的切换。如下:
// 如果从产品1切换到产品2,那么只需要把下面初始化factory1改为初始化factory2即可,其他地方无需改动。
// let factory1 = ConcreteFactory1()
let factory2 =ConcreteFactory2()
let productA = factory1.createProductA()
let productB = factory1.createProductB()
3.6 缺点
- 扩充产品的繁琐。抽象工厂虽然看起来已经很完美了,他可以应用于不同产品的切换,但是对于增加一个产品树,抽象工厂模式却是比较麻烦的。比如我们现在需要增加一个产品树C,针对于上面只有两个具体工厂类的情况,我们除了增加一个AbstractProductC类,还需要增加2个类:ConcreteProductC1和ConcreteProductC2,分别对应于ConcreteFactory1和ConcreteFactory2。除此之外,我们还要在AbstractFactory类中增加一个createProductC()接口、还要在ConcreteFactory1和ConcreteFactory2中实现这个新增的接口。
不难发现,当我们为抽象工厂模式增加一个产品树的时候,除了增加对应的产品类之外,还要对所有的工厂类(抽象工厂类和所有的具体工厂类)进行扩张,这种修改显然是麻烦且繁琐的。
抽象工厂模式中,侧重的是工厂类也就是产品簇,而非产品类。
3.7 使用场景
- 适用于程序中不同配置的切换。比如数据库的切换、屏幕主题的切换、夜间模式的切换等等。
- 不适用于扩展新的产品类。
(四)总结
- 简单工厂和工厂方法模式侧重的是产品类,工厂类是为了产品类而生的。对于抽象工厂模式,侧重的是工厂类,一个工厂类可以创建一个产品簇中的产品。
- 简单工厂对于增加新的产品是比较麻烦的,需要修改的地方比较多,工厂方法可以任意轻松地扩展新的产品类。
- 简单工厂模式适用于相对简单的情况和产品类不经常增减的地方。工厂方法模式是对简单工厂的改进,更加符合开放-封闭原则。抽象工厂模式是对工厂方法模式的进一步抽象,用来生产不同产品簇的全部产品,适用于不同产品切换的情景。和简单工厂一样,抽象工厂对产品的扩展支持的不好。
参考文章
抽象工厂模式-与-工厂方法模式区别
《大话设计模式》
文/VV木公子(作者)
PS:如非特别说明,所有文章均为原创作品,著作权归作者所有,转载请联系作者获得授权,并注明出处。
如果有技术问题,欢迎加入QQ群进行交流,群聊号码:194236752。