工厂模式
现实案例
- 假如我们有一个披萨店,通过一个 Pizza 类我们可以创建披萨;
- 同时在 Pizza 类之下,我们还可以创建很多不同的子类(芝士披萨,火腿披萨,牛肉披萨,等);
- Pizza 类中有一个
orderPizza
方法可以用来制作披萨; - 我们希望这个方法可以支持制作各种种类的披萨;
方法一:❌ 用 if/else 实现种类判断
方法一的问题
- 通过 if/else 来判断输入的字符串分辨类型;
- 这种方法没有对修改封闭。只要新种类的披萨加入,我们就要改 orderPizza 方法的代码;
方法二:✅ 用 “简单工厂” 来改写
- “工厂” 用来处理创建对象的细节;
- 简单工厂把创建实例对象的过程抽离出来,放到一个新的类中;
- 上面的
orderPizza
方法中,一堆 if/else 判断就是创建实例的具体过程; - 我们把它抽离出来,放到一个新的 SimplePizzaFactory 类中:
- SimplePizzaFactory 只做一件事情,就是帮用户创建披萨;
- 之后在 PizzaStore 类中有一个 SimplePizzaFactory 类型的实例属性,作为简单工厂的引用;
- 通过简单工厂的
createPizza
方法就可以创建想要类型的披萨了;
- 通过把创建披萨实例的过程提取出来,我们就可以复用这段代码了;
- 不光
orderPizza
方法可以用,以后在别的方法中也可以用; - “简单工厂” 并不是一种设计模式,只是一种编程习惯;

工厂方法模式
- 所有工厂模式都用来封装对象的创建;
- 工厂方法模式在 Creator 超类中定义了一个创建对象的接口,由子类去实现创建对象的具体过程;
- 这个创建对象的接口,被称为 “工厂方法”;
- 在 Creator 超类中定义的其他方法,都可以去调用这个工厂方法。但是在编写创建者类时,我们并不需要知道最后实际创建出来的实例是哪一个子类的;
- 工厂方法让子类最后决定创建出来的 “实例” 的类型;

- 通过工厂方法模式,我们让产品的 “实现” 从 “使用” 中解耦。即使增加或修改产品,Creator 也不会受影响,只需要增加或修改 ConcreteProduct 类就行了;
- 在 “简单工厂” 中,创建实例的是一个工厂对象。而在 “工厂方法模式” 中,超类定义了工厂方法接口,创建实例的是具体的子类;
- 超类中的工厂方法接口,不一定非是抽象的接口。也可以定义成一个默认的方法。这样即使创建者没有任何子类,也可以创建产品;
方法三:✅ 用工厂方法模式改写
- 先定义一个 PizzaStore 抽象类,在里面定义出抽象方法
createPizza
。这个方法用来实例化披萨,但具体实例化的过程需要子类去自己实现;
- 在这里我就不写 Pizza 类的实现了;
- 之后定披萨的话,我们需要先创建一个披萨店子类实例;
- 然后通过这个子类实例去创建披萨;

依赖倒置
- 在我们没有使用工厂模式之前,我们通过披萨店类来创建所有的披萨实例;
- 对披萨具体实现的任何改变都会影响到 PizzaStore。我们说 PizzaStore 依赖于披萨的实现;
- 每增加一个新的披萨种类,就等于 PizzaStore 多了一个依赖;

- 我们需要减少代码对于具体类的依赖;
- 依赖倒置原则:要依赖抽象,不要依赖具体类;
- 这个原则说明了:不能让高层组件依赖底层组件。而且,不管高层或底层组件,两者都应该依赖于抽象;
- 高层组件,是指由其他底层组件定义其行为的类;
- 例如:PizzaStore 就是个高层组件,它的行为由披萨定义;
- 而各种 Pizza 类就是底层组件;
- 通过上面 👆 的类图,可以看出,高层组件 PizzaStore 依赖具体的披萨类;
- 我们需要让代码,依赖抽象类,而不是依赖具体类。对于底层组件和高层组件都是如此;

- 很容易想到,我们把这堆披萨类,可以抽象出一个 Pizza 超类;
- 然后让 PizzaStore 只依赖这个 Pizza 超类;
- 同时所有的披萨子类,都是 Pizza 超类的具体实现;
- 这样就使得高层组件和底层组件都依赖于 Pizza 抽象了;
依赖倒置思考方式
- 设计代码时,不要先想 “我要如何创建所有的具体类”;
- 应该先想 “所有的具体类可以抽象成什么东西”;
- 例如 🌰:
- 不要先想披萨店如何创建所有的披萨实例;
- 应该先想所有的披萨可以抽象成一个 Pizza 超类;
- 然后我们让披萨店,用一个工厂方法去创建具体的披萨实例;
- 这样 “披萨店” 和 “各种披萨” 只需要依赖 Pizza 超类就行了;
- 下面是一些指导方针来避免违反依赖倒置原则:
- 变量不可以持有具体类的引用。如果使用
new
,就会持有具体类的引用,你可以改成工厂来避免; - 不要让类派生自具体类,如果派生自具体类,你就会依赖具体类,请派生自抽象(接口或者抽象类);
- 不要覆盖基类中已经实现的方法,如果覆盖,那么说明基类就不是一个真正适合被继承的抽象,基类中已经实现的方法,应该有所有的子类共享;
- 要尽量达到这些原则,但不是随时都要遵守;