设计模式-Factory

抽象工厂+工厂方法

问题

创建型模式可以解耦客户端和对象的创建过程,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 框架和泛型的特性,分别实现了"继承、组合"两种设计。

相关推荐
Asthenia041231 分钟前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz9651 小时前
ovs patch port 对比 veth pair
后端
Asthenia04121 小时前
Java受检异常与非受检异常分析
后端
uhakadotcom1 小时前
快速开始使用 n8n
后端·面试·github
JavaGuide1 小时前
公司来的新人用字符串存储日期,被组长怒怼了...
后端·mysql
bobz9651 小时前
qemu 网络使用基础
后端
Asthenia04122 小时前
面试攻略:如何应对 Spring 启动流程的层层追问
后端
Asthenia04122 小时前
Spring 启动流程:比喻表达
后端
Asthenia04122 小时前
Spring 启动流程分析-含时序图
后端
ONE_Gua3 小时前
chromium魔改——CDP(Chrome DevTools Protocol)检测01
前端·后端·爬虫