设计模式-创建型模式-生成器模式

26 May 2021

Reading time ~33 minutes

意图

生成器模式是一种创建型设计模式,使你能够分步骤创建复杂对象。该模式允许你使用相同的创建代码生成不同类型和形式的对象。

image-20210526120227915

问题

假设有这样一个复杂对象,在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中;甚至还有更糟糕的情况,那就是这些代码散落在客户端代码的多个位置。

image-20210526133654734

假设,我们来思考如何创建一个房屋House对象。建造一栋简单的房屋,首先你需要建造四面墙和地板,安装房门和一套窗户,然后再建造一个屋顶。但是如果你想要一栋更宽敞更明亮的房屋,还要有院子和其他设施,那又该怎么办呢?

最简单的方法是扩展房屋基类,然后创建一系列涵盖所有参数组合的子类。但最终你将面临相当数量的子类。任何新增的参数都会让这个层次结构更加复杂。

另一种方法则无需生成子类。你可以在房屋基类中创建一个包括所有可能参数的超级构造函数,并用它来控制房屋对象。这种方法确实可以避免生成子类,但它却会造成另外一个问题。

image-20210526134037708

通常情况下,绝大部分的参数都没有使用,这使得对于构造函数的调用十分不简洁。

解决方案

生成器模式建议将对象构造代码从产品类中抽取出来,并将其放在一个名为生成器的独立对象中。

image-20210526134215789

该模式会将对象构造过程划分为一组步骤,比如buildWalls创建墙壁和buildDoor创建房门等。每次创建对象时,你都需要通过生成器对象执行一系列步骤。重点在于你无需调用所有步骤,而只需调用创建特定对象配置所需的那些步骤即可。

当你需要创建不同形式的产品时,其中的一些构造步骤可能需要不同的实现。例如,木屋的房门可能需要使用木头制造,而城堡的房门则必须使用石头制造。

在这种情况下,你可以创建多个不同的生成器,用不同方式实现一组相同的创建步骤。然后就可以在创建过程中使用这些生成器来生成不同类型的对象。

image-20210526134609340

例如,假设第一个建造者是用木头和玻璃制造房屋,第二个建造者使用石头和钢铁,而第三个制造者使用黄金和钻石。在调用同一组步骤后,第一个建造者会给你一栋普通房屋,第二个会给你一座小城堡,而第三个则会给你一座宫殿。但是,只有在调用构造步骤的客户端代码可以通过通用接口与建造者进行交互时,这样的调用才能返回需要的房屋。

主管

你可以进一步将用于创建产品的一系列生成器步骤调用抽取成为单独的主管类。主管类可定义创建步骤的执行顺序,而生成器则提供这些步骤的实现。

image-20210526134957876

严格来说,你的程序中并不一定需要主管类。客户端代码可以直接以特定顺序调用创建步骤。不过,主管类中非常适合放入各种例行构造流程,以便在程序中反复使用。

此外,对于客户端代码来说,主管类完全隐藏了产品构造细节。客户端只需要将一个生成器与主管类关联,然后使用主管类来构造产品,就能从生成器处获得构造结果了。

生成器模式结构

image-20210611163710424

生成器模式适合应用场景

使用生成器模式可以避免“重叠构造函数(telescopic constructor)”的出现

假设你的构造函数中有十个可选参数,那么调用该函数会非常不方便;因此,你需要重载这个构造函数,新建几个只有较少参数的简化版。但这些构造函数仍需调用主构造函数,传递一些默认数值来替代省略掉的参数。

class Pizza{
	Pizza(int size){...}
	Pizza(int size, boolean cheese){...}
	Pizza(int size, boolean cheese, boolean pepperoni){...}
}

生成器模式让你可以分步骤生成对象,而且允许你仅使用必须的步骤。应用该模式后,你再也不需要将几十个参数塞进构造函数里了。

当你希望使用代码创建不同形式的产品时,可以使用生成器模式。

如果你需要创建的各种形式的产品,它们的制造过程相似且仅有细节上的差异,此时可使用生成器模式。

基本生成器接口中定义了所有可能的制造步骤,具体生成器将实现这些步骤来制造特定形式的产品。同时,主管类将负责管理制造步骤的顺序。

使用生成器构造组合树或其他复杂对象。

生成器模式让你能分步骤构造产品。你可以延迟执行某些步骤而不影响最终产品。你甚至可以递归调用这些步骤,这在创建对象树时非常方便。

生成器在执行制造步骤时,不能对外发布未完成的产品。这可以避免客户端代码获取到不完整结果对象的情况。

实现方式

  1. 清晰地定义通用步骤,确保它们可以制造所有形式的产品。否则你将无法进一步实施该模式

  2. 在基本生成器接口中声明这些步骤

  3. 为每个形式的产品创建具体生成器类,并实现其构造步骤

    不要忘了实现获取构造结果对象的方法。你不能在生成器接口中声明该方法,因为不同声称其构造的产品可能没有公共接口,因此你就不知道该方法返回的对象类型。但是,如果所有产品都位于单一类层次中,你就可以安全地在基本接口中添加获取生成对象的方法。

  4. 考虑创建主管类。它可以使用同一生成器对象来封装多种构造产品的方式

  5. 客户端代码会同时创建生成器和主管对象。构造开始前,客户端必须将生成器对象传递给主管对象。通常情况下,客户端只需要调用主管类构造函数一次即可。主管类使用生成器对象完成后续所有制造任务。还有一种方式,那就是客户端可以将生成器对象直接传递给主管类的制造方法

  6. 只有在所有产品都遵循相同接口的情况下,构造结果可以直接通过主管类获取。否则,客户端应当通过生成器获取构造结果

生成器模式优缺点

优点:

  • 可以分步创建对象,暂缓创建步骤或递归运行创建步骤
  • 生成不同形式的产品时,可以复用相同的制造代码
  • 单一职责原则。可以将复杂构造代码从产品的业务逻辑中分离出来

缺点:

  • 由于该模式需要新增多个类,因此代码整体复杂程度会有所增加

与其他模式的关系

  • 在许多设计工作的初期都会使用工厂方法模式,随后演化为使用抽象工厂模式、原型模式或生成器模式。
  • 生成器重点关注如何分步生成复杂对象。抽象工厂专门用于生产一系列相关对象。抽象工厂会马上返回产品,生成器则允许在获取产品前执行一些额外构造步骤。
  • 你可以在创建复杂组合模式树时使用生成器,因为这可使其构造步骤以递归的方式运行。
  • 你可以结合使用生成器和桥接模式:主管类负责抽象工作,各种不同的生成器负责实现工作。
  • 抽象工厂、生成器和原型都可以用单例模式来实现。

以上摘自REFACTORING GURU

代码示例

定义一个生成器接口:

public interface Builder {
    /**
     * 建造发动机
     */
    void createEngine();

    /**
     * 建造车轮
     */
    void createWheel();

    /**
     * 建造车壳
     */
    void createShell();

    /**
     * 建造车座
     */
    void createSeat();

    /**
     * 建造车载音响
     */
    void createHiFi();
}

定义具体生成器:

public class SuvBuilder implements Builder{
    @Override
    public void createEngine() {
        System.out.println("SUV发动机建造成功...");
    }

    @Override
    public void createWheel() {
        System.out.println("SUV车轮建造成功...");
    }

    @Override
    public void createShell() {
        System.out.println("SUV车壳建造成功...");
    }

    @Override
    public void createSeat() {
        System.out.println("SUV车座建造成功...");
    }

    @Override
    public void createHiFi() {
        System.out.println("SUV车载音响建造成功...");
    }

    public void getResult(){
        System.out.println("SUV完工!");
    }
}
public class SportCarBuilder implements Builder{
    @Override
    public void createEngine() {
        System.out.println("跑车发动机建造成功...");
    }

    @Override
    public void createWheel() {
        System.out.println("跑车车轮建造成功...");
    }

    @Override
    public void createShell() {
        System.out.println("跑车车壳建造成功...");
    }

    @Override
    public void createSeat() {
        System.out.println("跑车车座建造成功...");
    }

    @Override
    public void createHiFi() {
        System.out.println("跑车车载音响建造成功...");
    }

    public void getResult(){
        System.out.println("跑车完工!");
    }
}

定义主管类:

public class Director {
    public void createSuv(Builder builder){
        builder.createEngine();
        builder.createShell();
        builder.createSeat();
        builder.createWheel();
        builder.createHiFi();
    }

    public void createSport(Builder builder){
        builder.createSeat();
        builder.createShell();
        builder.createWheel();
        builder.createEngine();
    }
}

客户端代码:

public class Demo {
    public static void main(String[] args) {
        Director director = new Director();

        SportCarBuilder sportCarBuilder = new SportCarBuilder();
        director.createSport(sportCarBuilder);
        sportCarBuilder.getResult();

        SuvBuilder suvBuilder = new SuvBuilder();
        director.createSuv(suvBuilder);
        suvBuilder.getResult();
    }
}

//跑车车座建造成功...
//跑车车壳建造成功...
//跑车车轮建造成功...
//跑车发动机建造成功...
//跑车完工!
//SUV发动机建造成功...
//SUV车壳建造成功...
//SUV车座建造成功...
//SUV车轮建造成功...
//SUV车载音响建造成功...
//SUV完工!


Design PatternsProfessional Knowledge Share Tweet +1