抽象工厂+工厂方法
问题
创建型模式可以解耦客户端和对象的创建过程,Builder 是解决了"复杂对象的创建问题",Factory 则是让"具体对象的创建过程对客户端透明"。
客户端不需要知道自己创建的是哪一个具体类对象,而是交由 Factory 来实例化,其实现方法是:定义一个用于创建对象的接口,让接口的实现类来决定实例化具体的对象。
分类
Factory 一共有三种模式:
- 简单工厂:不在 23 种设计模式中
- 工厂方法:每个具体类的创建过程对应一个创建方法
- 抽象工厂:具体类的每种组合对应一个创建方法
示例和组成
简单工厂
简单工厂只有一个 Factory 对象,提供一个创建方法,通过传入不同的参数,返回不同的具体类对象,UML 和示例代码如下:
图 1. 简单工厂的 UML
简单工厂的代码示例:
java
public interface Product {
void print();
}
class ProductA implements Product {
@Override
public void print() {
//...输出逻辑;
}
}
class ProductB implements Product {
@Override
public void print() {
//...输出逻辑;
}
}
typescript
// 简单工厂
public class SimpleFactory {
public Product getInstance(int type) {
if(1 == type){
return new ProductA();
} else if(2 == brand){
return new ProductB();
}
return null;
}
public static void main(String[] args) {
SimpleFactory f = new SimpleFactory();
Product p = f.getInstance(1);
//...
}
}
简单工厂通过在创建方法中传入创建参数,根据创建参数来创建具体 Product 对象。
很显然,随着 Product 子类逐渐增多,每增加一个 Product 子类,就要修改一次 Factory 的代码,不符合"开闭原则"。
工厂方法
为了应对"Product 子类逐渐增多"带来的代码膨胀问题,工厂方法则是使用继承的方法,为每个 Product 子类创建对应的 Factory 子类,每个 Factory 子类创建对应的 Product 子类对象。
UML 和示例代码如下:
图 2. 工厂方法的 UML
工厂方法的示例代码:
java
public interface Factory {
Product getInstance();
}
public class ProductAFactory implements Factory {
@Override
public Product getInstance(){
return new ProductA();
}
}
public class ProductBFactory implements Factory {
@Override
public Product getInstance(){
return new ProductB();
}
}
和"简单工厂"相比,即使 Product 的子类不断增多,也不需要去修改某个 Factory 类的代码,只需要新建一个对应 Factory 实现类来扩展即可,符合了"开闭原则"。
工厂方法存在的问题在于:随着 Product 子类的增多,Factory 子类也必然增多,这是不可避免的,需要使用其他方法来解决代码的维护问题。
抽象工厂
针对"工厂方法"存在的问题,如果存在多种 Product,即产品族,不同产品之间存在组合关系,那么多个产品的一种组合关系,可以使用一个 Factory 来创建,避免实现过多的 Factory 类。
UML 和代码示例如下:
图 3. 抽象工厂 UML
示例代码:
java
public interface ProductA {
void print();
}
public class ProductA1 implements ProductA {
@Override
public void print() {
}
}
public class ProductA2 implements ProductA {
@Override
public void print() {
}
}
public interface ProductB {
void play();
}
public class ProductB1 implements ProductB {
@Override
public void play() {
}
}
public class ProductB2 implements ProductB {
@Override
public void play() {
}
}
java
public interface IFactory {
ProductA createProductA();
ProductB createProductB();
}
public class Factory1 implements IFactory {
@Override
public ProductA createProductA(){
return new ProductA1();
}
@Override
public ProductB createProductB(){
return new ProductB1();
}
}
public class Factory2 implements IFactory {
@Override
public ProductA createProductA(){
return new ProductA2();
}
@Override
public ProductB createProductB(){
return new ProductB2();
}
}
和"工厂方法"相比,抽象工厂可以组合多种 Product,从而减少 Factory 类的个数,降低了代码的复杂度和维护难度。
适用场景
工厂方法的适用场景:
- 当 client 不知道它要创建的对象所属的具体类时
- 当 client 希望由 factory 的子类来创建自己所需的对象时
工厂方法不再将特定应用有关的类固化在代码中,代码仅需处理 Product 接口,为将来的扩展提供了方便。
抽象工厂的适用场景:
- 一个系统要独立于它的产品的创建、组合和表示时
- 一个系统要由多个产品族中一个来配置时
- 当提供一个产品类库,只想显示他们的接口而不是实现时
抽象工厂分离了 client 和类的实现过程,并且易于改变产品族,只需要修改 Factory 接口的创建方式和组合方式,就可以生成一个新的产品族。
变和不变
设计模式的核心是分离问题中"变与不变"两个部分,在工厂模式中,不变的是"创建对象"这个要求,变化的是"创建什么对象"。
工厂模式分离了这两者:
- 定义抽象类 Factory 提供"创建对象"方法
- 由 Factory 子类来实现"创建具体的对象"
DDD 中的工厂
在 DDD 中也有使用到"工厂",不过和设计模式中的工厂相比,在 DDD 中,工厂只是用来创建领域模型中的对象,不承担其他的职责,是一个"职责单一"的对象,只是聚合根的行为之一。
如果是简单的创建过程,可以使用聚合根的静态方法 create 等,并非必须使用"工厂"。
小结
工厂模式中的三种模式,适用不同复杂度的场景,并且复杂度是逐渐上升的:
1、 在 product 分类很少时,可以通过简单工厂,传入参数来创建具体的对象。
2、随着 product 分类逐渐增加,每次都要修改简单工厂的方法,复杂度逐渐增加,从而改用"工厂方法",为每个 product 类设计一个 factory method,避免修改代码。
3、随着 product 分类进一步增加,并且多个 product 相互存在组合关系,复杂度进一步上升,所以由一个 factory method 负责组合多个 product 具体类,返回最终的复合对象。
可以看出,工厂模式是为了解决"由于代码膨胀带来创建对象越来越难维护"的问题。
在设计模式最佳套路5 ------ 愉快地使用工厂方法模式中,对抽象工厂做了进一步抽象,来降低重复代码,降低代码的维护难度,并使用 spring 框架和泛型的特性,分别实现了"继承、组合"两种设计。