工厂方法
顾名思义,工厂就是生产许多产品的地方。而这里的产品一般都有一些共性,但也会有些特色。比如生产iPhone的富士康,里面有iPhone 7, iPhone 7 plus, iPhone 8, iPhone X 等等。
现在苹果发布了万众瞩目的 iPhone Y。富士康要求我们新增这台iPhone Y,以便迅速铺开生产线。你打开他们的代码库一看,我的妈!长长的一串判断语句。
// iPhone.swift
var phone: iPhone
switch phoneMode {
case .iPhone7:
phone = iPhone7()
case .iPhone7Plus:
phone = iPhone7Plus()
case .iPhone8:
phone = iPhone8()
case .iPhoneX:
phone = iPhoneX()
}
// do something for phone
作为一个学习设计模式的少年,你是否感受到了坏代码的味道?
没错,这个违反了代码的 松散耦合 的初衷。一旦需要加入新的iPhone,我们不得不修改 iPhone.swift
源码。而其中还包含了其他的业务逻辑,很有可能在加入新功能的同时引入了新bug。而且 iPhone.swift
可能牵涉到其他的源码,所以就造成了编译时间以及代码部署时间的增加。
让我们想个办法 抽离经常改变的
,从而解耦合。于是工厂方法登场了。
// iPhoneFactory.swift
enum PhoneMode {
case iPhone7
case iPhone7Plus
case iPhone8
case iPhoneX
}
protocol iPhone {}
class iPhone7: iPhone {}
class iPhone7Plus: iPhone {}
class iPhone8: iPhone {}
class iPhoneX: iPhone {}
class iPhoneFactory {
func createIPhone(phoneMode: PhoneMode) -> iPhone {
var phone: iPhone
switch phoneMode {
case .iPhone7:
phone = iPhone7()
case .iPhone7Plus:
phone = iPhone7Plus()
case .iPhone8:
phone = iPhone8()
case .iPhoneX:
phone = iPhoneX()
}
}
}
当我们需要增加新的iPhone时,只需要修改iPhoneFactory
,而这个类只涉及到怎么创建 iPhone,所以更加安全和独立。
这么做的好处:
- 松散耦合。
- 复用。可能会有其他的类也想创建iPhone,那么我们就有这么一个集中的地方来处理这些请求,从而让这个工厂方法逻辑被尽可能地复用。
- 维护更容易。
抽象工厂 (Abstract factory)
苹果太受欢迎了,特别是我国人民对于iPhone的热情空前高涨。富士康的产能已经无法满足苹果了。于是苹果找到了穷且衰:“兄弟,发财的机会要不要?让你赢取白富美,走上人生巅峰。”
但为了保证品质,不能让穷且衰胡乱地生产iPhone。于是富士康把iPhoneFactory
抽象成一个模板,可以让别的iPhone工厂跟随这套模板生产iPhone。在保证品质的前提下,不失灵活性。抽象工厂就这么应运而生了。
protocol iPhoneFactory {
func createIPhone() -> iPhone
}
extension iPhoneFactory {
// default version
func createIPhone() -> iPhone {
var phone: iPhone
switch phoneMode {
case .iPhone7:
phone = iPhone7()
case .iPhone7Plus:
phone = iPhone7Plus()
case .iPhone8:
phone = iPhone8()
case .iPhoneX:
phone = iPhoneX()
}
}
}
class FuShiKangIPhoneFactory: iPhoneFactory {
func createIPhone() -> iPhone {
// FuShiKang's iPhone
}
}
class QiongZeShuaiIPhoneFactory: iPhoneFactory {
func createIPhone() -> iPhone {
// QiongZeShuai's iPhone
}
}
总结
工厂方法 - 定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
抽象工厂 - 提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
工厂方法和抽象工厂有着千丝万缕的联系。我的理解是当需要有多个工厂的时候就把简单的工厂方法抽象成一个模板,变成抽象工厂,然后再让一个又一个的工厂方法去实现这个抽象模板。而在一些简单的情景中,我们可以只创建一个简单的工厂方法就好。
当然抽象工厂也有一个缺点:对于抽象工厂模板本身的扩展困难。一旦要对模板本身增加新的方法,这会牵涉到大量已有的具体的工厂方法的实现。另外新增接口是否适应于老的工厂类也是个问题。
另外,我们从工厂方法中更可以学到一个非常重要但晦涩的Design原则 - [依赖倒置原则 - Dependency Inversion Principle](依赖反转原则 - Wikiwand)。它有两条重要的属性:
- 依赖抽象,不依赖具体类。
- 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。
以这里的iPhone为例,富士康和穷则衰都依赖iPhone7, iPhone7 Plus, iPhone 8, iPhone X 这些低层的class。这会造成一个问题,如果我们增加了新的iPhone或者改变旧的就会牵扯到高层的各个类,这对于维护是噩梦。所以我们把 iPhone7, iPhone 7 Plus, iPhone 8, iPhone X 的共性都抽象出来成为一个接口 (Protocol)。于是所以新建的 iPhone都要实现这个接口,它们只依赖于这个抽象的接口。而这些高层类也只需知道 iPhone这个接口,并不需要知道更多的细节,比如它到底是iPhone X还是iPhone 8。大家都只依赖抽象而不是具体的类。
这就有了松耦合
的好处,同时维护起来也就更容易。
再说说倒置
。一般都是高层组件依赖低层的API。现在关系被倒过来了,大家依赖于抽象的接口。这里有一张图清晰地描述了这种关系。
所以,依赖倒置原则把问题上升到了更抽象,更具普世意义的境界。看穿了世间的变与不变。
网友评论