建造者模式-复杂对象的组装与创建

生产一辆车,主要有以下步骤:安装骨架、安装发动机及安装轮胎。这些步骤有指定的执行顺序,步骤缺一不可。

图 传统方案

传统方案存在的问题:

  1. 传参不便,虽可在构造函数那传参,但是传参时需要注意参数顺序等。
  2. 扩展不便,例如新增一种车型,创建步骤不同,这时需要修改Car的源码,不符合开闭原则。
  3. 客户端在创建对象的时候,需要关心内部的创建细节。对象与对象创建耦合在一起。

1 建造者模式

将客户端与包含多个组成部分(或部件)的复杂对象的创建过程分离,客户端无需关心复杂对象的内部组成部分与装配方式,只需要知道所需的建造者类型即可。

创建者模式关注如何一步步地创建一个复杂对象,不同的具体建造者定义了不同的创建过程,且具体的建造者相互独立,增加新的建造者非常方便,无须修改已有代码,系统具有较好的扩展性。

图 建造者模式结构图

Product: 产品角色,是被构建的复杂对象,包含多个部件。具体建造者创建该产品的内部表示并定义其装配过程。

Builder: 抽象建造者,为创建一个产品Product对象的各个部件指定抽象接口。一般声名两类方法:一类方法是buildPartX(),用于创建复杂对象的各个部件;另一类方法是getResult(),用于返回复杂对象。Builder既可以是抽象类,也可以是接口。

ConcreteBuilder: 具体建造者,实现Builder接口,实现各个部件的具体构造和装配方法,定义并明确其所创建的复杂对象,也可以提高一个方法返回创建好的复杂产品对象。

Director: 指挥者,又称为导演类,负责安排负责对象的建造次序。在其construct()方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互。

复制代码
public class Car {

    private String body;

    private String engine;

    private String tire;

    public void setBody(String body) {
        this.body = body;
    }

    public void setEngine(String engine) {
        this.engine = engine;
    }

    public void setTire(String tire) {
        this.tire = tire;
    }

    @Override
    public String toString() {
        return "Car{" +
                "body='" + body + '\'' +
                ", engine='" + engine + '\'' +
                ", tire='" + tire + '\'' +
                '}';
    }
}

public abstract class CarBuilder {

    protected final Car car = new Car();

    public abstract void installBody();

    public abstract void installEngine();

    public abstract void installTire();

    public Car getResult() {
        return car;
    }

}

public class AudiCarBuilder extends CarBuilder{

    @Override
    public void installBody() {
        car.setBody("奥迪-车身");
    }

    @Override
    public void installEngine() {
        car.setEngine("奥迪-发动机");
    }

    @Override
    public void installTire() {
        car.setTire("奥迪-轮胎");
    }

}

public class BenzCarBuilder extends CarBuilder{

    @Override
    public void installBody() {
        car.setBody("奔驰-车身");
    }

    @Override
    public void installEngine() {
        car.setEngine("奔驰-发动机");
    }

    @Override
    public void installTire() {
        car.setTire("奔驰-轮胎");
    }
}

public class CarDirector {

    private final CarBuilder carBuilder;

    public CarDirector(CarBuilder carBuilder) {
        this.carBuilder = carBuilder;
    }

    public Car construct() {
        carBuilder.installBody();
        carBuilder.installEngine();
        carBuilder.installTire();
        return carBuilder.getResult();
    }
}

public class Client {

    public static void main(String[] args) {
        CarBuilder carBuilder;
        carBuilder = new BenzCarBuilder();

        CarDirector carDirector = new CarDirector(carBuilder);
        System.out.println(carDirector.construct());
    }

}

1.1 省略Director

在有些情况下,为了简化系统结构,可以将Director和抽象建造者Builder进行合并,在Builder中提供逐步构建复杂产品对象的construct()方法。将上面的Builder及Client类修改为:

复制代码
public abstract class OmitCarBuilder {

    protected final Car car = new Car();

    public abstract void installBody();

    public abstract void installEngine();

    public abstract void installTire();

    public Car construct() {
        installBody();
        installEngine();
        installTire();
        return car;
    }

}

public class Client {

    public static void main(String[] args) {
        OmitCarBuilder omitCarBuilder = new AudiOmitCarBuilder();
        System.out.println(omitCarBuilder.construct());
    }

}

省略方式不影响系统的灵活性和可扩展性,同时还简化了系统结构,但加重了抽象建造者类的职责。如果construct()方法较为复杂,待构建产品的组成部分较多,建议还是将construct()方法单独封装在Director中。这样更符合单一职责原则。

1.2 钩子方法

增加钩子方法来控制是否调用某个buildPartX()方法。

构造方法的返回类型通常为boolean,方法名一般为isxxx (xxx为属性名)。构造方法定义在抽象建造者类中。构造方法来决定某属性(组件)是否需要添加。

例如,上面需求做了修改:奥迪车不需要装发动机,而奔驰车需要安装。

复制代码
public abstract class HookCarBuilder {

    protected Car car = new Car();

    public abstract void installBody();

    public abstract void installEngine();

    public abstract void installTire();

    public boolean isBody() {
        return false;
    }

    public boolean isEngine() {
        return false;
    }

    public boolean isTire() {
        return false;
    }

    public Car construct() {
        if (!isBody()) installBody();
        if (!isEngine()) installEngine();
        if (!isTire()) installTire();
        return car;
    }

}

public class AudiHookCarBuilder extends HookCarBuilder{
    @Override
    public void installBody() {
        car.setBody("奥迪-车身");
    }

    @Override
    public void installEngine() {
        car.setEngine("奥迪-发动机");
    }

    @Override
    public void installTire() {
        car.setTire("奥迪-轮胎");
    }

    @Override
    public boolean isEngine() {
        return true;
    }
}

通过钩子方法,可以对复杂产品的构建进行精细控制。可以控制buildPartX()方法的执行顺序,还可以控制是否需要执行某个buildPartX()方法。

2 优缺点

优点:

  1. 客户端不必知道产品内部组成细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同产品对象。
  2. 每个具体建造者都相对独立,而与其他具体建造者无关。用户使用不同的建造者即可生成不同的对象。系统扩展方便,符合开闭原则。
  3. 可以更加精细化地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便控制。

缺点:

  1. 所创建的产品一般具有较多的共同点,其组成部分相似。如果产品之间的差异性很大,就不适合使用建造者模式。
  2. 如果产品内部结构复杂且多变,可能需要定义很多具体建造者类来实现这种变化,这会让系统变得臃肿。

3 适用场景

  1. 需要生成的产品对象有复杂的内部结构,包含多个成员变量。
  2. 生成的产品对象属性相互依赖,需要指定其生成顺序。
  3. 隔离复杂对象的创建和适用,并使得相同的创建过程可以创建不同的产品。
相关推荐
探索java8 分钟前
Spring MVC框架中全局异常处理机制详解
java·spring·mvc
意疏22 分钟前
浙江大学PTA程序设计C语言基础编程练习题6-10
c语言·开发语言
AI必将改变世界27 分钟前
【软考系统架构设计师备考笔记5】 - 专业英语
java·开发语言·人工智能·笔记·系统架构·英语
_祝你今天愉快30 分钟前
Java Lock
android·java·后端
listhi52031 分钟前
Python实现信号小波分解与重构
开发语言·python·重构
熊猫片沃子1 小时前
mybatis 与mybatisplus 比较总结
java·后端·mybatis
骑驴看星星a1 小时前
层次分析法代码笔记
开发语言·笔记·python·numpy
Elastic 中国社区官方博客2 小时前
升级 Elasticsearch 到新的 AWS Java SDK
java·大数据·elasticsearch·搜索引擎·云计算·全文检索·aws
枣伊吕波2 小时前
十一、请求响应-请求:简单参数和实体参数(简单实体参数与复杂实体参数)
java·spring boot·后端
苇柠2 小时前
SpringMVC基础
java·后端·spring