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

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

图 传统方案

传统方案存在的问题:

  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. 隔离复杂对象的创建和适用,并使得相同的创建过程可以创建不同的产品。
相关推荐
长安初雪2 分钟前
Java客户端SpringDataRedis(RedisTemplate使用)
java·redis
繁依Fanyi21 分钟前
使用 Spring Boot + Redis + Vue 实现动态路由加载页面
开发语言·vue.js·pytorch·spring boot·redis·python·算法
aloha_78927 分钟前
B站宋红康JAVA基础视频教程(chapter14数据结构与集合源码)
java·数据结构·spring boot·算法·spring cloud·mybatis
星尘安全28 分钟前
一种新的电子邮件攻击方式:AiTM
开发语言·网络安全·php·网络钓鱼·aitm
尘浮生36 分钟前
Java项目实战II基于Java+Spring Boot+MySQL的洗衣店订单管理系统(开发文档+源码+数据库)
java·开发语言·数据库·spring boot·mysql·maven·intellij-idea
鸽芷咕37 分钟前
【Python报错已解决】xlrd.biffh.XLRDError: Excel xlsx file; not supported
开发语言·python·机器学习·bug·excel
铁匠匠匠1 小时前
【C总集篇】第八章 数组和指针
c语言·开发语言·数据结构·经验分享·笔记·学习·算法
猿饵块1 小时前
cmake--get_filename_component
java·前端·c++
编程小白煎堆1 小时前
C语言:枚举类型
java·开发语言
秋邱1 小时前
C++: 类和对象(上)
开发语言·c++