设计模式之建造者模式(含链式建造)

开始之前

大家都知道建造者模式是23种设计模式之一,其属于创建型模式,我们可以先看一下菜鸟教程对建造者模式的介绍。

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

意图

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

主要解决

需要解决的问题灵活性要求较高,无需在意建造过程中内部细节可使用此模式。

类图

在这里插入图片描述

建造者角色

  • Product(产品类):需要生成的目标对象。
  • Builder(抽象建造类):抽象的建造者类,定义抽象建造者的目的。
  • ConcreteBuilder(具体建造者):实现抽象建造者类,组建产品,实现要生成的产品对象。
  • Director(导演类):负责调用适当的建造者来组建产品(本例子中没有使用多个建造方式,真实开发中也并不多见)。

代码实现

接下来我使用组装电脑的例子来展示建造者一步步建造的魅力。

  • Computer - 目标类
package com.example.factory.builder.traditional;

import lombok.Data;

/**
 * @author 墨
 */
@Data
public class Computer {
    private String cpu;

    /**
     * 主板
     */
    private String motherboard;

    /**
     * 显示器
     */
    private String monitor;

    /**
     * 键盘
     */
    private String keyboard;

    /**
     * 鼠标
     */
    private String mouse;
}
复制代码

Computer即Product,这个电脑产品就是一个我们最终所需要的,它由CPU、主板、显示器、键盘、鼠标组成。

  • ComputerBuilder - 抽象建造者
package com.example.factory.builder.traditional;

/**
 * 
 * @author 墨
 */
public interface ComputerBuilder {
    Computer computer = new Computer();
    default Computer getComputer() {
        return computer;
    }

    void buildCpu(String cpu);
    void buildMotherboard(String motherboard);
    void buildMonitor(String monitor);
    void buildKeyboard(String keyboard);
    void buildMouse(String mouse);
}
复制代码

ComputerBuilder 是一个Builder类,我们在里面定义了建造所需要的方法,包含了构建CPU、主板、显示器、键盘、鼠标的方法。

下面我们来创建一个ConcreteBuilder,SpecificComputerBuilder实现了ComputerBuilder,并且实现了抽象建造的一些细节。

  • SpecificComputerBuilder 具体建造者
package com.example.factory.builder.traditional;

/**
 * 
 * @author 墨
 */
public class SpecificComputerBuilder implements ComputerBuilder {

    @Override
    public void buildCpu(String cpu) {
        computer.setCpu(cpu);
    }

    @Override
    public void buildMotherboard(String motherboard) {
        computer.setMotherboard(motherboard);
    }

    @Override
    public void buildMonitor(String monitor) {
        computer.setMonitor(monitor);
    }

    @Override
    public void buildKeyboard(String keyboard) {
        computer.setKeyboard(keyboard);
    }

    @Override
    public void buildMouse(String mouse) {
        computer.setMouse(mouse);
    }
}
复制代码

接下来我们创建一个测试方法,一步一步建造,输入我们所需要的产品,最终构建出我们所需要的电脑产品

package com.example.factory.builder.traditional;

/**
 * @author 墨
 */
public class TestBuilder {
    public static void main(String[] args) {
        SpecificComputerBuilder computerBuilder = new SpecificComputerBuilder();
        computerBuilder.buildCpu("i5 cpu");
        computerBuilder.buildMotherboard("蓝天主板");
        computerBuilder.buildMonitor("小米显示器");
        computerBuilder.buildMouse("双飞燕鼠标");
        computerBuilder.buildKeyboard("双飞燕键盘");

        Computer computer = computerBuilder.getComputer();
        System.out.println(computer);
    }
}
复制代码

控制台打印出了我们所需要的产品信息
sumchr
至此,我们的建造者就完成了组装电脑的建造工作。

升级-链式调用

前文我们谈论到建造时,创建一个具体建造者每次都使用SpecificComputerBuilder 的方法去构建一个产品的属性,这不是很符合我们编码的习惯,也不符合建造者一步步建造的思想,特别是当一个产品的属性多起来之后我们要写很多行这样类似的代码,不优雅,也不美观,而链式建造刚好能解决这样的问题;接下来我向大家介绍一下链式调用。

方法一:改造前文

修改抽象建造者内的方法,令其组装过程中返回自身,同时修改具体的建造类。

package com.example.factory.builder.traditional;

/**
 * 
 * @author 墨
 */
public interface ComputerBuilder {
    Computer computer = new Computer();
    default Computer setComputer() {
        return computer;
    }

    ComputerBuilder buildCpu(String cpu);
    ComputerBuilder buildMotherboard(String motherboard);
    ComputerBuilder buildMonitor(String monitor);
    ComputerBuilder buildKeyboard(String keyboard);
    ComputerBuilder buildMouse(String mouse);
}
复制代码
package com.example.factory.builder.traditional;

/**
 * 
 * @author 墨
 */
public class SpecificComputerBuilder implements ComputerBuilder {

    @Override
    public ComputerBuilder buildCpu(String cpu) {
        computer.setCpu(cpu);
        return this;
    }

    @Override
    public ComputerBuilder buildMotherboard(String motherboard) {
        computer.setMotherboard(motherboard);
        return this;
    }

    @Override
    public ComputerBuilder buildMonitor(String monitor) {
        computer.setMonitor(monitor);
        return this;
    }

    @Override
    public ComputerBuilder buildKeyboard(String keyboard) {
        computer.setKeyboard(keyboard);
        return this;
    }

    @Override
    public ComputerBuilder buildMouse(String mouse) {
        computer.setMouse(mouse);
        return this;
    }
}
复制代码

在测试类内测试链式调用

package com.example.factory.builder.traditional;

/**
 * @author 墨
 */
public class TestBuilder {
    public static void main(String[] args) {
        SpecificComputerBuilder computerBuilder = new SpecificComputerBuilder();
        computerBuilder.buildCpu("i5 cpu");
        computerBuilder.buildMotherboard("蓝天主板");
        computerBuilder.buildMonitor("小米显示器");
        computerBuilder.buildMouse("双飞燕鼠标");
        computerBuilder.buildKeyboard("双飞燕键盘");

        Computer computer = computerBuilder.setComputer();
        System.out.println(computer);
        
        
        Computer chain = new SpecificComputerBuilder().buildCpu("i3").buildMotherboard("白云主板").buildMonitor("大米显示器")
                .buildMouse("单飞燕鼠标").buildKeyboard("单飞燕键盘").setComputer();
        System.out.println(chain);
    }
}
复制代码

sumchr
我们发现使用链式调用之后的建造者简洁了很多,再也不需要面对一坨硬硬的代码了。这也是建造者的一个重要特点。

方法二:最终整合

我们直接忽略Director、Product类,只关注建造本身。

package com.example.factory.builder.chain;

import lombok.ToString;

/**
 * @author 墨
 */
@ToString
public class ComputerBuilder {
    private String cpu;
    private String motherboard;
    private String monitor;
    private String keyboard;
    private String mouse;

    public ComputerBuilder(String motherboard, String monitor, String keyboard, String mouse,String cpu) {
        this.motherboard = motherboard;
        this.monitor = monitor;
        this.keyboard = keyboard;
        this.mouse = mouse;
        this.cpu = cpu;
    }

    public static Builder newBuilder() {
        return new Builder();
    }
    
    public static class Builder {
        private String cpu;
        private String motherboard;
        private String monitor;
        private String keyboard;
        private String mouse;

        public Builder() {
        }

        public Builder setCpu(String cpu) {
            this.cpu = cpu;
            return this;
        }

        public Builder setMotherboard(String motherboard) {
            this.motherboard = motherboard;
            return this;
        }

        public Builder setMonitor(String monitor) {
            this.monitor = monitor;
            return this;
        }

        public Builder setKeyboard(String keyboard) {
            this.keyboard = keyboard;
            return this;
        }

        public Builder setMouse(String mouse) {
            this.mouse = mouse;
            return this;
        }
        
        public ComputerBuilder builder() {
            return new ComputerBuilder(motherboard,monitor,keyboard,mouse,cpu);
        }
    }
}

复制代码

测试一下

package com.example.factory.builder.chain;

/**
 * @author 墨
 */
public class ChainBuilderTest {
    public static void main(String[] args) {
        ComputerBuilder builder = ComputerBuilder.newBuilder().setCpu("i3").setMotherboard("白云主板")
                .setMonitor("大米显示器").setMouse("单飞燕鼠标")
                .builder();
        System.out.println(builder);
    }
}
复制代码

观察一下控制台输出
sumchr
这样,我们的链式建造者就大功告成了~

一些细节

  1. 当建造时如果没有创建所有细节时会有空值

我们可以通过Builder的构造方法来设置默认值

public Builder() {
	cpu = "默认cpu";
    motherboard = "默认主板";
    monitor = "默认显示器";
    keyboard = "默认键盘";
    mouse = "默认内存";
}
复制代码
  1. 当建造时如果创建的参数不符合要求的一些处理

可以在具体建造时对入参一些限制及过滤,例如:输入i5时我们觉得这个CPU太烂了,直接换个更更更好的

public ComputerBuilder builder() {
    if ("i5".equals(cpu)) {
        cpu = "i3";
    }
    return new ComputerBuilder(motherboard,monitor,keyboard,mouse,cpu);
}
复制代码

建造者模式就介绍到这里,如果本文对你有用请务必给个 👍