在面向对象的世界里,工厂模式被广泛地应用于项目中,也许你没听过,但你肯定用过。简单地来说,工厂模式地出现源于增加程序的可扩展性,降低耦合度。之所以叫工厂模式,是用工厂生产产品来形象比如代码中生产对象的过程。
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
简单工厂模式 | simple Factory Pattern
Before patterns:
首先设计两种汽车,分别实现 “Auto” 接口。
1 | public interface Auto{ |
2 | //所有汽车都可以驾驶 |
3 | public void drive(); |
4 | } |
5 | |
6 | //小轿车 |
7 | public class Car implements Auto{ |
8 | |
9 | public void drive(){ |
10 | System.out.println("小轿车启动了..."); |
11 | } |
12 | } |
13 | |
14 | public class Bus implements Auto{ |
15 | |
16 | public void drive(){ |
17 | System.out.println("大巴车启动了"); |
18 | } |
19 | } |
现在我们来实现一个简单工厂类:
1 | public class AutoFactory{ |
2 | //生产汽车 |
3 | public Auto proudce(String name){ |
4 | if("car".equals(name)){ |
5 | return new Car(); |
6 | }else if("bus".equals(name)){ |
7 | return new Bus(); |
8 | } |
9 | } |
10 | } |
简单工厂将创建对象的逻辑封装起来(Unsteady),将实现隐藏起来有很多好处,在工厂类中你可以添加所需的生产成品的逻辑代码。试想一下,如果一个类的new改动了(new可能涉及到很多参数),那需要在每个实现的地方都去改动,这是一件多么可怕的事情。
但是问题来了,简单工厂不符合 开放封闭 原则(对扩展开放,对修改关闭),当需要增加汽车的种类的时候,必须要修改 produce 的代码。
所谓对扩展开放,对修改关闭的含义,简而言之就是,当增加新的功能的时候,不需要通过修改原有的代码来实现(即原有的代码都是有价值的),而只是增加新的代码,代码具有向后兼容性。
工厂方法模式 | Factory Method Pattern
产品经理刚接到一个电话,工厂要扩大规模了,现在要生产卡车(Trunk)了,你稍加思索,太简单了,只需要新增一个 Trunk 类,并在简单工厂类AutoFactory 的 produce 函数里增加一个判断分支就可以了。
1 | public class Trunk implements Auto{ |
2 | |
3 | public void drive(){ |
4 | System.out.println("Trunk start drive..."); |
5 | } |
6 | } |
1 | public class AutoFactory{ |
2 | public Auto proudce(String name){ |
3 | if("car".equals(name)){ |
4 | return new Car(); |
5 | }else if("bus".equals(name)){ |
6 | return new Bus(); |
7 | } // 增加新的分支来解决需求 |
8 | else if("Trunk".equals(name)){ |
9 | return new Trunk(); |
10 | } |
11 | } |
12 | } |
为什么我们要对修改关闭?
这里不针对工厂模式。当新的需求增加,如果我们需要的是去修改原有的代码,这是很不利的,一则改动可能很大,二则,其实你并不知道哪些地方需要改动(这里不仅针对工厂模式处理的问题),所以你可能会浏览整个的代码,而忐忑不安地去改动,需求的不可预料性,让你的代码很不稳定,很有可能在未来地某个时间节点遇到问题(源于你没有发现的需要改动的地方)。所以,在我们设计代码的时候,就应该将代码设计成扩展类型的,而非修改型的,这样即便出了问题,也仅仅是一些 easy case,你也很容将应该扩展的部分补齐,而不是整个系统的崩溃。
聊完了这个设计原则,我们再来看看简单工厂 (simple factory),它把对象的创建放在一个工厂类中,通过参数来创建不同的对象,缺点就是每添加一种类型的对象,就要对原有代码进行修改,虽然仅仅是添加一个 switch-case 分支,但依然不符合 “不改代码” 的原则。
那我们有什么办法来解决这个问题呢?
我们如何想不修改原来的代码似乎很难,因为工厂在那呢,我们要生成肯定要用到工厂,问题似乎陷入僵局,但是转念一想,如果不能修改原有的工厂,那我们可以将抽象层次提高,将工厂抽象成一个接口,当抽象层次提高的时候,以前的工厂其实就算是底层了,而底层的东西是可以多种实现的,即便工厂也不例外,我们可以新建新的工厂的实现来生产我们要的东西,来避免对原有工厂进行修改。
我们首先设计一个通用的工厂接口
1 | public interface IAutoFactory{ |
2 | //生产汽车 ,name也是非必要的,不过可以对一开始的默认的几个产品有效果 |
3 | public Auto produce(String name); |
4 | } |
5 | |
6 | // 原有的工厂类也不需要拆分了,那部分仍可沿用默认的 string 传参方法实现就可以了,当然也可以拆分哈 |
卡车工厂
1 | public class TrunkAutoFactory implements IAutoFactory{ |
2 | |
3 | public Auto produce(String name){ |
4 | return new Trunk(); |
5 | } |
6 | } |
抽象本就是一个很抽象的词,他和封装是对应的,有封装就有抽象,它门的作用就是隐藏细节,提供通用接口。所以,其实设计模式也是对应于这一OO的核心思想。还记得看到过一句话。
如果一个问题解决不了,那就给它加一层抽象
IAutoFactory就是对 factory 的一种抽象,使得 Factory 的实现也可变,更加灵活。
使用卡车工厂:
1 | IAutoFactory factory = new TrunkAutoFactory(); |
2 | Auto car = factory.produce(null); |
3 | car.drive(;) |
抽象工厂模式 | Abstract Factory
仅仅是工厂方法的复杂化,保存了多个 new,大工程才用得上。
我们继续对汽车工厂,接下来工厂要扩大规模,开始涉足配件(上层决定涉足汽车大灯业务,针对已有车型生产前大灯)。
那我们该怎么做呢?
和车一样,定义灯的基类(或者接口),定义灯的工厂基类(接口)。然后定义不同的大灯工厂么?
其实我们没必要,我们可以在原来的工厂(IAutoFactory)里面进行扩展,增加一个生产灯的方法即可。
1 | public interface IAutoFactory{ |
2 | //生产汽车 |
3 | public Auto produce(); |
4 | |
5 | //生产大灯 |
6 | public Light produceLight; |
7 | } |
从工厂模式过渡到抽象工厂模式,并不需要太大的改动,只需要在原有的工厂中增加一些方法即可。(可是这不是不符合对修改封闭么)
抽象工厂模式中,我们可以定义实现不止一个接口,一个工厂也可以生产不只一个产品类,抽象工厂较好的实现了 “开放-封闭” 原则,是三个模式中较为抽象,并具一般性的模式
实际上,抽象工厂模式也存在缺点,可能细心的你发现了,在抽象工厂模式下,增加新的产品族很容易,但是增加新的产品结构则很难,需要改动 IFactory ,以及每个实现的 FactoryImpl.
抽象工厂模式的这种性质被称为 “开闭原则”的倾斜性 . 正因为抽象工厂模式存在“开闭原则”的倾斜性,它以一种倾斜的方式来满足“开闭原则”,为增加新产品族提供方便,但不能为增加新产品结构提供这样的方便,因此要求设计人员在设计之初就能够全面考虑,不会在设计完成之后向系统中增加新的产品等级结构,也不会删除已有的产品等级结构,否则将会导致系统出现较大的修改,为后续维护工作带来诸多麻烦。