设计模式4-建造者模式

建造者模式的定义

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计出模式属于创建型模式,它提供了一种创建对象的最佳方式。

一个Builder类会一步一步构造最终的对象。该Builder类是独立于其他对象的。

举例
电脑是由CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的复杂产品,卖家一般不会自己去组装电脑,而是将电脑的各种配置要求告诉销售公司,销售公司安排专业的技术人员进行电脑的组装,并将组装好的电脑交付卖家。


意图
将一个复杂的构建与其表示相分离,使得同样的构造过程可以创建不同的表示

主要解决
主要解决在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。

何时使用
一些基本部件不会变,而其组合经常变化的时候。

如何解决
将变与不变分离开。

关键代码

  • 建造者:创建和提供实例;
  • 导演:管理建造出来的实例的依赖关系;

优点

  • 封装性好,构建和表示分离。
  • 扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
  • 客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。

缺点

  • 产品的组成部分必须相同,这限制了其使用范围。
  • 如内部变化复杂,会有很多的建造类
  • 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。

使用场景

  • 相同的方法,不同的执行顺序,产生不同的结果。
  • 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
  • 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
  • 初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。

注意事项

  1. 与工厂模式的区别是:
  • 建造者模式更加注重方法的调用顺序,工厂模式注重创建对象。
  • 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的对象都一样。
  • 关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。
  • 建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样。
  1. 应用过程中可以根据需要改变,如果创建的产品种类只有一种,只需要一个具体建造者,这时可以省略掉抽象建造者,甚至可以省略掉指挥者角色。

建造者模式的结构

结构图如下:
image.png
主要角色:

  • 产品(Product):包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
  • 建造者抽象(Builder):是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法getResult()。
  • 具体建造者(ConcreteBuilder):实现Builder接口,完成复杂产品的各个部件的具体创建方法。
  • 调用者(Director):调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

建造者模式的实现

下面以组装电脑演示建造者模式。

首先有一个电脑类(产品)

/**
 * 产品-电脑
 *
 * @author ccheng
 * @date 2021/12/19
 */
@Data
public class Computer {
    /**
     * cpu
     */
    private String cpu;
    /**
     * 显卡
     */
    private String graphics;
    /**
     * 内存
     */
    private String memory;
    /**
     * 硬盘
     */
    private String disk;

    @Override
    public String toString() {
        return "电脑{" +
                "cpu='" + cpu + '\'' +
                ", 显卡='" + graphics + '\'' +
                ", 内存='" + memory + '\'' +
                ", 硬盘='" + disk + '\'' +
                '}';
    }
}

然后有一个抽象的建造者-电脑组装工人

/**
 * 抽象建造者-电脑组装工人
 *
 * @author ccheng
 * @date 2021/12/19
 */
@Slf4j
public abstract class AbstractComputerBuilder {
    /**
     * 具体产品
     */
    protected Computer computer = new Computer();

    /**
     * 选择CPU
     */
    abstract void selectCpu(String cpu);

    /**
     * 选择显卡
     */
    abstract void selectGraphics(String graphics);

    /**
     * 选择内存
     */
    abstract void selectMemory(String memory);

    /**
     * 选择硬盘
     */
    abstract void selectDisk(String disk);

    /**
     * 组装电脑
     *
     * @return
     */
    public Computer build() {
        log.info("开始组装电脑......");
        log.info("第一步:安装CPU:" + computer.getCpu());
        log.info("第二步:安装内存:" + computer.getMemory());
        log.info("第三步:安装显卡:" + computer.getGraphics());
        log.info("第四步:安装硬盘:" + computer.getDisk());
        log.info("第五步:安装电源、显示器、键盘、鼠标......");
        log.info("组装电脑完成:" + computer);
        return computer;
    }
}

然后有品牌组装机的建造者,品牌组装机配置固定不能变

/**
 * 抽象建造者-联想品牌电脑的组装工人
 * 固定配置:英特尔 i5-11600k cpu + 核显 + 金士顿 8g 内存 + 西部数据蓝盘 1t 硬盘
 *
 * @author ccheng
 * @date 2021/12/19
 */
@Slf4j
public class LenovoComputerBuilder extends AbstractComputerBuilder{
    private static final String DEFAULT_CPU = "英特尔 i5-11600k";
    private static final String DEFAULT_GRAPHICS = "核显";
    private static final String DEFAULT_MEMORY = "金士顿 8g";
    private static final String DEFAULT_DISK = "西部数据蓝盘 1t";

    public LenovoComputerBuilder() {
        computer.setCpu(DEFAULT_CPU);
        computer.setGraphics(DEFAULT_GRAPHICS);
        computer.setMemory(DEFAULT_MEMORY);
        computer.setDisk(DEFAULT_DISK);
    }

    @Override
    void selectCpu(String cpu) {
        log.info("固定配置不可更改");
    }

    @Override
    void selectMemory(String memory) {
        log.info("固定配置不可更改");
    }

    @Override
    void selectGraphics(String graphics) {
        log.info("固定配置不可更改");
    }

    @Override
    void selectDisk(String disk) {
        log.info("固定配置不可更改");
    }
}

也可以自己DIY配置

/**
 * 抽象建造者-自定义配置的组装工人
 *
 * @author ccheng
 * @date 2021/12/19
 */
@Slf4j
public class CustomComputerBuilder extends AbstractComputerBuilder {

    @Override
    void selectCpu(String cpu) {
        computer.setCpu(cpu);
        log.info("客户选择了{} cpu", cpu);
    }

    @Override
    void selectMemory(String memory) {
        computer.setMemory(memory);
        log.info("客户选择了{} 内存", memory);
    }

    @Override
    void selectGraphics(String graphics) {
        computer.setGraphics(graphics);
        log.info("客户选择了{} 显卡", graphics);
    }

    @Override
    void selectDisk(String disk) {
        computer.setDisk(disk);
        log.info("客户选择了{} 硬盘", disk);
    }
}

还有一个电脑店老板,指挥工人干活

/**
 * 指挥者-电脑店老板
 *
 * @author ccheng
 * @date 2021/12/19
 */
@Slf4j
public class ShopBoss {
    public Computer getComputer(AbstractComputerBuilder builder, double fee) {
        if (fee <= 0) {
            log.info("想打劫?给个毛线你");
            return null;
        } else {
            log.info("收到{}元,开始干活吧", fee);
            return builder.build();
        }
    }
}

最后客户可以选择买品牌电脑,也可以自己自定义电脑的配置

/**
 * 客户
 *
 * @author ccheng
 * @date 2021/12/19
 */
@Slf4j
public class Client {
    public static void main(String[] args) {
        //电脑店老板
        ShopBoss shopBoss = new ShopBoss();
        //买一台联想品牌组装机
        AbstractComputerBuilder lenovoBuilder = new LenovoComputerBuilder();
        Computer lenovoComputer = shopBoss.getComputer(lenovoBuilder, 4000);
        log.info("买到联想品牌组装机:{}", lenovoComputer);
        log.info("##########################################");
        //买一台DIY组装机
        AbstractComputerBuilder customBuilder = new CustomComputerBuilder();
        customBuilder.selectCpu("英特尔 i7-11700k");
        customBuilder.selectGraphics("英伟达 P1000");
        customBuilder.selectMemory("金士顿DDR5 16g");
        customBuilder.selectDisk("三星980 500g");
        Computer computerComputer = shopBoss.getComputer(customBuilder, 8000);
        log.info("买到DIY组装机:{}", computerComputer);
    }
}

//运行结果
收到4000.0元,开始干活吧
开始组装电脑......
第一步:安装CPU:英特尔 i5-11600k
第二步:安装内存:金士顿 8g
第三步:安装显卡:核显
第四步:安装硬盘:西部数据蓝盘 1t
第五步:安装电源、显示器、键盘、鼠标......
组装电脑完成:电脑{cpu='英特尔 i5-11600k', 显卡='核显', 内存='金士顿 8g', 硬盘='西部数据蓝盘 1t'}
买到联想品牌组装机:电脑{cpu='英特尔 i5-11600k', 显卡='核显', 内存='金士顿 8g', 硬盘='西部数据蓝盘 1t'}
##########################################
客户选择了英特尔 i7-11700k cpu
客户选择了英伟达 P1000 显卡
客户选择了金士顿DDR5 16g 内存
客户选择了三星980 500g 硬盘
收到8000.0元,开始干活吧
开始组装电脑......
第一步:安装CPU:英特尔 i7-11700k
第二步:安装内存:金士顿DDR5 16g
第三步:安装显卡:英伟达 P1000
第四步:安装硬盘:三星980 500g
第五步:安装电源、显示器、键盘、鼠标......
组装电脑完成:电脑{cpu='英特尔 i7-11700k', 显卡='英伟达 P1000', 内存='金士顿DDR5 16g', 硬盘='三星980 500g'}
买到DIY组装机:电脑{cpu='英特尔 i7-11700k', 显卡='英伟达 P1000', 内存='金士顿DDR5 16g', 硬盘='三星980 500g'}

整个过程,客户不需要知道电脑是怎么组装的,但是整个电脑配置都是客户可以自己选择的,也就是构造与表示分离。电脑店老板也不关心建造的细节,他只负责指挥。

当然根据应用的不同,可以省略掉抽象建造者,甚至可以省略掉指挥者角色。同时为了使用方便,我们还会加上链式编程。

比如说电脑店老板刚创业,还请不起工人,这个时候老板自己又是建造者又是指挥者,我们对上面的建造者类和指挥者类进行合并,电脑类不变。

/**
 * 电脑组装者(又是工人,又是老板)
 *
 * @author ccheng
 * @date 2021/12/19
 */
@Slf4j
public class ComputerBuilder {
    /**
     * 默认配置方案
     */
    private static final String DEFAULT_CPU = "英特尔 i5-11600k";
    private static final String DEFAULT_GRAPHICS = "核显";
    private static final String DEFAULT_MEMORY = "金士顿 8g";
    private static final String DEFAULT_DISK = "西部数据蓝盘 1t";

    /**
     * 具体产品
     */
    protected Computer computer = new Computer();

    /**
     * 选择CPU
     */
    ComputerBuilder selectCpu(String cpu) {
        computer.setCpu(cpu);
        //链式编程,返回this
        return this;
    }

    /**
     * 选择显卡
     */
    ComputerBuilder selectMemory(String memory) {
        computer.setMemory(memory);
        return this;
    }

    /**
     * 选择内存
     */
    ComputerBuilder selectGraphics(String graphics) {
        computer.setGraphics(graphics);
        return this;
    }

    /**
     * 选择硬盘
     */
    ComputerBuilder selectDisk(String disk) {
        computer.setDisk(disk);
        return this;
    }

    /**
     * 组装电脑
     *
     * @return
     */
    public Computer build() {
        log.info("开始组装电脑......");
        //如果客户没有自定义配置,使用默认配置
        if (computer.getCpu() == null) {
            computer.setCpu(DEFAULT_CPU);
        }
        if (computer.getGraphics() == null) {
            computer.setGraphics(DEFAULT_GRAPHICS);
        }
        if (computer.getMemory() == null) {
            computer.setMemory(DEFAULT_MEMORY);
        }
        if (computer.getDisk() == null) {
            computer.setDisk(DEFAULT_DISK);
        }
        log.info("第一步:安装CPU:" + computer.getCpu());
        log.info("第二步:安装内存:" + computer.getMemory());
        log.info("第三步:安装显卡:" + computer.getGraphics());
        log.info("第四步:安装硬盘:" + computer.getDisk());
        log.info("第五步:安装电源、显示器、键盘、鼠标......");
        log.info("组装电脑完成:" + computer);
        return computer;
    }
}

客户端使用变化不大,依据是客户选择配置,建造者根据配置组装成品,不过由于加入链式编程,调用过程变得更加简单易懂

/**
 * 客户
 *
 * @author ccheng
 * @date 2021/12/19
 */
@Slf4j
public class Client {
    public static void main(String[] args) {
        ComputerBuilder builder = new ComputerBuilder();
        Computer computer = builder.selectCpu("英特尔 i7-11700k")
                .selectGraphics("英伟达 P1000")
                .selectMemory("金士顿DDR5 16g")
                .selectDisk("三星980 500g")
                .build();
        log.info("买到DIY组装机:{}", computer);
    }
}

//运行结果
开始组装电脑......
第一步:安装CPU:英特尔 i7-11700k
第二步:安装内存:金士顿DDR5 16g
第三步:安装显卡:英伟达 P1000
第四步:安装硬盘:三星980 500g
第五步:安装电源、显示器、键盘、鼠标......
组装电脑完成:电脑{cpu='英特尔 i7-11700k', 显卡='英伟达 P1000', 内存='金士顿DDR5 16g', 硬盘='三星980 500g'}
买到DIY组装机:电脑{cpu='英特尔 i7-11700k', 显卡='英伟达 P1000', 内存='金士顿DDR5 16g', 硬盘='三星980 500g'}

源码中的建造者模式

jdk中的java.lang.StringBuilder类,它提供了各种重载的append方法,给我们开放构造步骤,最后调用toString()方法就可以获得一个构造好的完整字符串。
image.png

还有spring中的BeanDefinitionBuilder,mybatis中的CacheBuilder等

posted on 2021-12-19 22:59  _ccheng  阅读(36)  评论(0编辑  收藏  举报