3.3.1工厂方法模式(3.3) 返回目录
工厂方法模式(factory method pattern)从2方面认识。
- 结构推导。参数化工厂使用分支语句违背OCP,如果以多态来重构分支结构,可以从静态工厂模式演化出工厂方法模式的结构。
- 使用意图。工厂方法模式并非作为静态工厂模式的改良版本。换言之,它并不是为了支持Demo→IMan,而是为了解决类型匹配问题。
1.以多态来重构分支结构
以多态来重构if-else、switch-case等分支结构是重构的基本技术,在参数化工厂的代码(见例程3-10)基础上,通过重构可以获得工厂方法模式的结构。,如例程3-14所示。
重构的目标是将分支结构的每一个分支,放在一个子类的@Override函数中,而父类型的抽象方法,由包含分支结构的函数如create承担,create的参数如typeName用于分支判断,则删除该参数。
重构过程如下:
①从包含分支结构的函数create获得抽象方法,并封装到抽象类型IManFactory中。注意将静态方法改为实例方法。
②IManFactory的各子类型创建一一对应的IMan的子类型。package chap3.factoryP;
import chap3.init.IMan;
public interface IManFactory{
public IMan create();
}
package chap3.factoryP;
import chap3.init.*;//IMan类层次
public class BoyFactory implements IManFactory{
@Override public Boy create(){
return new Boy();
}
}//IMan的每一个子类型有一个IManFactory具体工厂对应,略
// chap3.factoryP.Demo
public static void test(){
IMan man = new BoyFactory().create();//new Boy()
man.foo();
}
它体现了一种非常重要的思考方式——以多态来重构if-else或switch-case结构。从重构分支结构的角度看,策略模式与[2.1.3工厂方法模式(3.3)]和[4.2状态模式(5.8)]是三胞胎。
2.解决类型匹配
工厂方法模式的目的,它不同于静态工厂模式、God、Spring,后者都是依赖抽象类型的使能工具,工厂方法模式不是!
很多程序员在认识到静态工厂模式的缺点后,会认为工厂方法模式是静态工厂模式对升级版。GoF对工厂方法模式的描述为:『定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类』。这种解释给人的印象是,若Demo→IMan,工厂方法模式在遇到添加IMan新子类时,不再需要不修改静态工厂的代码而是添加新的工厂子类,符合OCP。
GoF在工厂方法模式的适应性中指出:“当一个类不知道它所需要的对象的类时”,即Demo不知道它需要IMan的具体子类。这也使得很多程序员认为工厂方法模式是抽象依赖原则的使能工具。但是,Demo从不依赖IMan的具体子类变成依赖IManFactory的具体子类,仍然违反了抽象依赖原则。绝大多数情况下,这种使用方式是多此一举,是典型的为了模式而模式的做法。
程序员可以使用通用的工具God和Spring,或者静态工厂模式这种不完美但简洁明快的工具,满足Demo→IMan。
使用工厂方法模式的合理情景/最佳实践,当Demo同时依赖有强烈的对应关系的两个类层次IA和IB时,它解决类型匹配问题。
例如Demo依赖交通工具/IVehicle和修理店/IRepairShop。IVehicle有多个子类型如轿车、单车和牛车,如果IVehicle对象出现问题,则需要进行修理。显而易见,Demo不应该分别地创建IVehicle-IRepairShop对象,否则理论上不能够保证它们匹配,如将轿车拖到了单车修理店。于是,在IVehicle中设计一个抽象方法也即工厂方法,以获得对应的IRepairShop 对象。代码如例程3-12所示。
package chap3.factoryP; //其他类包语句略
public interface IRepairShop{//产品类
void repair();
}//有Car4s、路边轿车修理店Low、单车修理店等,略
public interface IVehicle{ //工厂类
public void move();
public IRepairShop getRepairShop(int money); //工厂方法
}
import static yqj2065.util.Print.pln;
public class Car implements IVehicle{
public void move(){
pln("Car move");
}
public IRepairShop getRepairShop(int money){
if(money>3000){
return new Car4s();
}else{
return new Low();
}
}
}
//Demo
public static void main(String[] args) {
IVehicle car =new Car();
car.move();
IRepairShop repair = car.getRepairShop(2500); //
repair.repair();
car.move();
}
工厂方法模式的最佳实践,是Demo关注和依赖工厂类层次IVehicle,而对于工厂的产品即IRepairShop对象,Demo甚至可以一无所知。代码中创建具体的工厂对象如new Car()的这种用法,暗示工厂方法模式不是抽象依赖原则的使能工具。
★工厂方法模式(factory method pattern),解决类型匹配问题。Client依赖有强烈的对应关系的两个类层次IA和IB,则IA定义一个工厂方法以获得与IA对象匹配的IB对象。
例如,在创建链表时,应用程序员通常不介意使用new表达式,如List list = new ArrayList()。而JDK的设计者,不可能希望/要求程序员创建一个数据结构的同时,再去创建对应的数据结构的迭代器对象。因此,各种数据结构如List都是接口Iterable(可迭代的)的子类型,Iterable封装一个抽象方法,该方法是一个工厂方法——返回一个迭代器Iterator。
public interface Iterable{
Iterator iterator();//返回一个迭代器
}
编程实践中,通常工厂是Client关注的主体,而工厂生产的产品被Client所忽视。例如Client开车ICar,不管什么车Client都可以开。现在,Car要找它的4S店/I4S,Client会关注各种4S店吗?
最后更新2021.2.18 修改例子。