简单工厂、工厂方法、抽象方法。
简单工厂 —— Factory.create(any-object-class)
工厂方法 —— Factory ObjectFactory
抽象方法 —— 不同的 Factory 对于不同的任务有不同的实现方式。

DI 容器,基于工厂模式,在程序启动时,根据配置事先创建好对象。当需要时,直接从容器中获取。

  • 配置解析(创建哪些对象、什么时候创建对象)、
  • 对象创建(反射等)
  • 生命周期管理(新对象 or 复用、延迟加载、初始化/销毁钩子)

工厂模式分类:简单工厂、工厂方法、抽象工厂。前两个在项目中应用较多。

1 解决的问题

工厂模式的出发点/目的在于:解决创建对象时过于复杂的问题。

  1. 复杂的逻辑判断情况
  2. 复杂的对象依赖情况

复杂的逻辑判断情况
快递运输时,不同情况的货物存在不同的运输方式。例如,不紧急/距离近的货物通过货车运送,紧急的/距离远的货物通过飞机运送。同样类比奶茶店,奶茶店产出不同类型的奶茶。

if type = "cup1": return new Cup1();
else if type = "cup2": return new Cup2();
else ...
// switch-case

复杂的对象依赖情况
复杂对象依赖类似于:new A(new B(new C())new A(new B(), new C())

2 简单工厂(Simple Factory)

** 简单工厂 ** 模式,由 ** 抽象产品 具体产品 工厂/创建者 ** 三个要素组成,工厂内有具体的逻辑去判断生成怎么样的产品。

2.1 具体实现

产品

// 抽象产品
public abstract class Fruit {}
 
// 具体产品
public class Apple extends Fruit {}
public class Pear extends Fruit {}

工厂

public class FruitFactory {
    public Fruit create(String type) {
        switch (type) {
            case "苹果":
                AppleSeed appleSeed = new AppleSeed();
                Sunlight sunlight = new Sunlight();
                Water water = new Water();
                return new Apple(appleSeed, sunlight, water);
            case "梨子":
                return new Pear();
            default:
                throw new IllegalArgumentException("暂时没有这种水果");
        }
    }
}

调用者

public class User {
    private void eat(){
        FruitFactory fruitFactory = new FruitFactory();
        Fruit apple = fruitFactory.create("苹果");
        Fruit pear = fruitFactory.create("梨子");
        apple.eat();
        pear.eat();
    }
}

创建对象复杂的语句可以都扔到工厂里去实现,调用者调用时只需要简单调用就可以。

2.2 简单工厂的弊端

  • 如果需要生产的产品过多,此模式会导致工厂类过于庞大,变成超级类。当不同的奶茶生成过程需要修改时,都要修改此类,违背了单一职责原则。
  • 当要生产新的产品时,必须在工厂类中添加新的分支。而开闭原则告诉我们:类应该对修改封闭。我们希望在添加新功能时,只需增加新的类,而不是修改既有的类,所以这就违背了开闭原则。

3 工厂方法模式(Factory Method)

工厂方法针对简单工厂弊端,对简单工厂进行改进,由** 抽象产品 具体产品 抽象工厂 具体工厂 ** 四个要素组成。

虽然工厂方法代码量增加了,但是解决了关键问题,同时避免了简单工厂的两种弊端。

3.1 模式结构

3.2 具体实现

苹果工厂

public class AppleFactory {
	public Fruit create(){
		AppleSeed appleSeed = new AppleSeed();
		Sunlight sunlight = new Sunlight();
		Water water = new Water();
		return new Apple(appleSeed, sunlight, water);
	}
}

梨子工厂

public class PearFactory {
    public Fruit create(){
        return new Pear();
    }
}

调用者:

public class User {
    private void eat(){
        AppleFactory appleFactory = new AppleFactory();
        Fruit apple = appleFactory.create();
        PearFactory pearFactory = new PearFactory();
        Fruit pear = pearFactory.create();
        apple.eat();
        pear.eat();
    }
}

4 适用场景

场景 1:无法预知对象确切类别及其依赖关系。
将创建产品的代码与实际使用产品的代码分离,从而能在不影响其他代码的情况下扩展产品创建部分代码。

需要更改创建情况时,只需要更改工厂类及其子类即可,无需更改其他内容。

场景 2:用户能扩展内部组件。

场景 3:复用现有对象来节省系统资源,类似工具类。

在处理大型资源密集型对象 (比如数据库连接、文件系统和网络资源) 时,你会经常碰到这种资源需求。

  1. 首先,你需要创建存储空间来存放所有已经创建的对象。
  2. 当他人请求一个对象时,程序将在对象池中搜索可用对象。
  3. …然后将其返回给客户端代码。
  4. 如果没有可用对象,程序则创建一个新对象 (并将其添加到对象池中)。

可能最显而易见,也是最方便的方式,就是将这些代码放置在我们试图重用的对象类的构造函数中。但是从定义上来讲,构造函数始终返回的是新对象,其无法返回现有实例。

因此,你需要有一个既能够创建新对象,又可以重用现有对象的普通方法。这听上去和工厂方法非常相像。

5 优缺点

优点:

  • 你可以避免创建者和具体产品之间的紧密耦合。
  • 单一职责原则。你可以将产品创建代码放在程序的单一位置,从而使得代码更容易维护。
  • 开闭原则。无需更改现有客户端代码,你就可以在程序中引入新的产品类型。

缺点:

  • 应用工厂方法模式需要引入许多新的子类,代码可能会因此变得更复杂。最好的情况是将该模式引入创建者类的现有层次结构中。

6 TODO

把书读薄 | 《设计模式之美》设计模式与范式(创建型-工厂模式) - 掘金

工厂模式与 DI 容器的关系

DI 容器底层最基本的设计思路就是基于工厂模式的,相当于一个大工厂,负责在程序启动时,根据配置(创建哪些类对象、对象创建需要依赖哪些其他类对象) 事先创建好对象。当程序需要用到某个类对象时,直接从容器中获取即可。正是因为它持有一堆对象,所以这个框架才被称为 “容器”。

DI 容器的三个核心功能

  • 配置解析:通过一种形式让应用告知 DI 容器要创建哪些对象,一般通过配置文件形式,如 Spring 的配置文件;
  • 对象创建:DI 容器根据配置文件提供的信息创建对象,Java 中一般通过反射来动态加载类、创建对象;
  • 生命周期管理:返回新对象还是复用、是否延迟加载、是否配置对象初始化/销毁的钩子等;

课程中写了一个实现简单 DI 容器的例子,比较简单,只列下流程:最小原型设计提供执行入口配置文件解析核心工厂类设计