Java设计模式-创建者模式-建造者模式(Builder模式)

岳庆锦

发布于 2022.03.20 19:21 阅读 1647 评论 0

建造者模式(Builder模式)

 在软件开发过程中,有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件一定的步骤组合而成,各个部件可以灵活选择,但其创建步骤都大同小异。这类产品的创建无法用前面介绍的工厂模式实现,只有用建造者模式可以很好地描述该类产品的创建。

 定义 指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示 。

 它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。

 “建造者模式”的核心思想不在于部件的创建,具体产品的信息是在具体建造者类找那个体现出来的,不管部件是从怎么来的,是什么牌子的,建造者模式强调的是装配过程。部件的创造可以用工厂模式来实现。(可以将建造者模式和工厂模式相结合使用)

 通过一个例子来理解定义例如,计算机是由CPU、内存条、主板、硬盘等组件组装而成的。

 主机是“复杂对象”

 把各个组件装配成一个主机的过程是“复杂对象的构造过程”

 最后组装完的主机(也就是最后的成品)是“表示”

 采购员去买一台电脑时,不用关注所用组件的品牌以及如何装配的,只需要关注最终买的是什么牌子的电脑,这体现了“将复杂对象的构造与表示分离(解耦)”

 同样的装配过程,不同组件组合成的成品(表示)不一样,是对“同样的构建过程可以创建不同的表示”的解释。

 结构

 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。

 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法。

 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。

 指挥者(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

 结构类图:

 应用:下面通过“创建共享单车”的实例来理解建造者模式。

 分析:生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的;车座有橡胶,真皮等材质。对于自行车的生产就可以使用“建造者模式”来实现。

 类图中的Bike是产品类,包含车架和车座这两个组件(为了简单就不设计单独的车架类和车座类了,用字符串表示);Builder是抽象建造者,MoibleBuilder和OfoBuilder是具体建造者;Director是指挥者。

1.自行车类(Bike,产品类),包含字符串类型的车架和车座两个成员变量,及其对应的get和set方法。

public class Bike {

    //声明组件
    private String frame;//车架
    private String seat;//车座

    public String getFrame() {
        return frame;
    }

    public void setFrame(String frame) {
        this.frame = frame;
    }

    public String getSeat() {
        return seat;
    }

    public void setSeat(String seat) {
        this.seat = seat;
    }
}

2.抽象建造者类(Builder),聚合了产品类的对象bike(提高代码的复用性,具体子类直接给父类成员变量的属性赋值就OK),提供生产车架,车座的抽象方法,通过creatBike()方法返回构造完成后的产品实例。

public abstract class Builder {

    //聚合Bike类型的对象
    protected Bike bike = new Bike();

    //构造车架
    public abstract void buildFrame();

    //构造车座
    public abstract void buildSeat();

    //构造自行车的方法
    public abstract Bike creatBike();
}

3.摩拜单车(MobileBike,具体建造者),实现父类中的抽象方法,建造碳纤维车架和真皮车座,通过creatBike()返回具有碳纤维车架和真皮车座的摩拜单车。(最后返回的bike就是super.bike)

public class MobileBuilder extends Builder{
    @Override
    public void buildFrame() {
        bike.setFrame("碳纤维车架");
    }

    @Override
    public void buildSeat() {
        bike.setSeat("真皮车座");
    }

    @Override
    public Bike creatBike() {
        return bike;//返回父类的bike
    }
}

4.Ofo单车(OfoBike,具体建造者),实现父类中的抽象方法,建造铝合金车架和橡胶车座,通过creatBike()返回具有铝合金车架和橡胶车座的Ofo单车。

public class OfoBuilder extends Builder{
    @Override
    public void buildFrame() {
        bike.setFrame("铝合金车架");
    }

    @Override
    public void buildSeat() {
        bike.setSeat("橡胶车座");
    }

    @Override
    public Bike creatBike() {
        return bike;
    }
}

5.指挥者类(Director),聚合了抽象建造者类的对象builder,提供构造方法和控制组装过程的方法construct()。

public class Director {

    //声明Builder类型的对象
    private Builder builder;

    //创建Director对象的时候进行赋值
    public Director(Builder builder) {
        this.builder = builder;
    }

    //组装自行车
    public Bike construct(){
        builder.buildFrame();
        builder.buildSeat();
        return builder.creatBike();
    }
}

6.测试类(Client),以摩拜单车为例,在new指挥者类的对象时,把所需具体产品类型赋值给此对象,通过construct()方法获取最终成品(表现)。通过getFrame()和getSeat()方法获取具体成品各个组件的名称,检验最后的成品是不是我们想要的。

public class Client {
    public static void main(String[] args) {
        //创建指挥者对象
        Director director = new Director(new MobileBuilder());
        //让指挥者指挥组装自行车
        Bike bike = director.construct();

        System.out.println(bike.getFrame());
        System.out.println(bike.getSeat());
    }
}

7.运行结果

 注意,上面示例是“建造者模式”的常规用法,指挥者类Director在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类。但是,有些情况下需要简化系统结构,可以把指挥者类和抽象建造者类进行结合。

 结合方式:把指挥者功能加到抽象建造者中,使其不光构建组件,还要进行组装。

 结合后代码如下:

public abstract class Builder {

    //聚合Bike类型的对象
    protected Bike bike = new Bike();


    //构造车架
    public abstract void buildFrame();

    //构造车座
    public abstract void buildSeat();

    //构造自行车的方法(组装完成后,返回实例)
    public abstract Bike creatBike();


    public Bike construct(){
        this.buildFrame();
        this.buildSeat();
        return this.creatBike();
    }
}

 说明:这样做确实简化了系统结构,但同时也加重了抽象建造者类的职责,也是不太符合单一职责原则,如果construct()过于复杂,建议还是封装到Director中。

 “原型模式”优缺点

 优点

 建造者模式的封装性很好。

这一优点体现在两个方面:

1.使用建造者模式可以有效的封装变化(这里的变化是指组件,后面业务逻辑发生变化,比如新增一种新型单车或者一种单车被淘汰了,此时只需要增减具体建造者类)。

2.在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此将主要业务逻辑封装在指挥者类中,这对整体而言可以取得比较好的稳定性。(产品及其组件的构建比较稳定,一般只需要修改指挥者类的构造过程)

 在建造者模式中,客户端不必知道产品内部组成的细节(采购员采购电脑时不用关注电脑的组件和组成过程,只需关注买的电脑的牌子),将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象(相同的创建过程,组件不同,最后的成品也不一样)。

 可以更加精细地控制产品的创建过程(和第二条意思相近)。将复杂产品的创建步骤分解在不同的方法中(例如案例中,创建车座:buildSeat(),创建车架:buildFrame()),使得创建过程更加清晰,也更方便使用程序来控制创建过程。

 建造者模式很容易进行扩展。

如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险,符合“开闭原则”。

 缺点

 “建造者模式”所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式(例如,自行车和电脑组成部分差异很大,组建过程也十分不同,建造者模式就不适用)。因此,其使用范围受到一定的限制。

 使用场景

 建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。

 ①创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的构造顺序是稳定的。

 ②创建复杂对象的算法独立于该对象的组成部分及它们的装配方式,即产品的构建过程和最终的表示是独立的。

============================================================================================================

建造者模式——模式扩展

 建造者模式除了上面的用途外,在开发中还有一个常用的使用方式。

 就是当一个类构造器(构造方法)需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。

 重构前代码如下

1.手机类(Phone),包含手机的四个组件及其对应的set和get方法,toString()方法。

public class Phone {
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;

    public Phone(String cpu, String screen, String memory, String mainboard) {
        this.cpu = cpu;
        this.screen = screen;
        this.memory = memory;
        this.mainboard = mainboard;
    }

    public String getCpu() {
        return cpu;
    }

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

    public String getScreen() {
        return screen;
    }

    public void setScreen(String screen) {
        this.screen = screen;
    }

    public String getMemory() {
        return memory;
    }

    public void setMemory(String memory) {
        this.memory = memory;
    }

    public String getMainboard() {
        return mainboard;
    }

    public void setMainboard(String mainboard) {
        this.mainboard = mainboard;
    }

    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }
}

2.测试类(Client)

public class Client {
    public static void main(String[] args) {

        //构建Phone对象
        Phone phone = new Phone("intel","三星屏幕","金士顿","华硕");
        System.out.println(phone);
    }
}

3.运行结果

 进行构造:给Phone类创建构造方法(传入的参数类型为Builder类型),然后把构造方法私有。在手机类中声明静态内部类Builder,在建造者Builder中构建成员变量,然后把构建好的成员变量直接赋值给Phone(这一操作在Phone的构造方法里完成),最后返回构建好的手机类对象。

 重构后代码如下

1.手机类(Phone)

public class Phone {

    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;

    //私有构造方法(把构建者的成员变量直接赋值给Phone)
    private Phone(Builder builder){
        this.cpu = builder.cpu;
        this.screen = builder.screen;
        this.memory = builder.memory;
        this.mainboard = builder.mainboard;
    }

    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }

    //静态内部类
    public static final class Builder{
        private String cpu;
        private String screen;
        private String memory;
        private String mainboard;

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

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

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

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

        //使用构建者创建phone对象
        public Phone build(){
            return new Phone(this);//内部类可以直接访问外部类,这里的this是当前构造者类的对象
        }
    }
}

2.测试类(Client),把构建顺序由指挥者交给了用户,因为cpu()、screen()、memory()、mainboard()返回值都是当前Builder对象,所以可以用链式编程。

用链式编程的好处:可读性高,用哪个组件一目了然。

public class Client {
    public static void main(String[] args) {

        //创建手机对象->通过构建者对象创建手机对象
        Phone phone = new Phone.Builder()
                .cpu("Intel")
                .screen("三星屏幕")
                .memory("金士顿内存条")
                .mainboard("华硕主板")
                .build();

        System.out.println(phone);//默认调用toString()方法
    }
}

3.运行结果

重构后的代码使用起来更方便,某种程度上也可以提高开发效率。但从软件设计上,对程序员要求比较高。