设计模式初探-创建型模式之建造者模式

书接上回单例模式保证唯一实例,工厂模式把控创建过程。本篇中要说到建造者模式的另一个重要成员建造者模式(Build Pattern)。本文主要分为以下部分:

  1. 前言
  2. 建造者模式
    2.1 为什么要有建造者模式
    2.2 定义
    2.3 实现
    2.3.1 建造者模式的变种
    2.3.2 标准建造者模式
    2.3.3 区别及联系
  3. 总结

1. 前言

设计模式按照模式的目标可以做如下划分:

  • 创建型:涉及到对象的实例化,提供一个方法,将客户从对象的创建中解耦
  • 行为型:涉及到类和对象如何交互及分配职责
  • 结构型:结构型模式可以让你把类和对象组合到更大的对象中去

前面我们介绍了创建型模式的两位大佬,单例模式 以及 工厂模式。这篇文章里我们要重点介绍下创建者模式。

2. 建造者模式

2.1 为什么要有建造者模式

假设我们有如下的Bean,需要通过构造函数构造

public class Order {
    private String orderId;
    private String orderAddr;
    private String orderNotes;
    private String orderComments;
    private String userId;
    private String goodsId;
    
    public Order(){
        
    }
    
    public Order(String orderId, String orderAddr, String orderNotes, String orderComments, String userId, String goodsId) {
        this.orderId = orderId;
        this.orderAddr = orderAddr;
        this.orderNotes = orderNotes;
        this.orderComments = orderComments;
        this.userId = userId;
        this.goodsId = goodsId;
    }
}

现在我们尝试通过构造函数初始化它,我们会写出如下代码

        Order order=new Order("pcm001","上海市黄浦区陆家嘴","请尽快送达","五星好评","user001","goods001");

当然这段代码运行起来是不会有任何问题的,但是如果我们不小心写反了参数的顺序会发生什么呢?
尝试将orderNotes和orderAddr赋值搞反

        Order order=new Order("pcm001","请尽快送达","上海市黄浦区陆家嘴","五星好评","user001","goods001");

因为都是String类型的参数,所以编译时不会报错,程序能正常运行,那么我们什么时候才能发现问题呢?
或许只有产生脏数据,由数据倒查的时候才会发现问题所在
对程序而言,我们往往希望提前暴露问题,而不是运行时才暴露问题
这个问题有什么好的解决方案吗?答案就在建造者模式中。

2.2 定义

构造者模式将对象的创建和对象的表示分离,使得同样的构建过程可以构建不同的对象。正如标准设计模式的定义一样,创建型模式的定义同样晦涩难懂,只看定义,真是为难我等吃瓜群众。

别方,我们结合实例来看。

2.3 实现

2.3.1 建造者模式的变种

对于上面的Order类,我们可以做如下改造

  1. 定义静态内部类Builder
  2. 实现Builder类中的与属性名同名的赋值方法
  3. 实现Build类中的build方法(创建Order实例,并通过构造函数一一赋值)
  4. 在Order类中暴露静态builder方法,返回Builder()实例
public class Order {
    private String orderId;
    private String orderAddr;
    private String orderNotes;
    private String orderComments;
    private String userId;
    private String goodsId;
    //私有构造函数
    private Order() {

    }
   //私有构造函数
    private Order(String orderId, String orderAddr, String orderNotes, String orderComments, String userId, String goodsId) {
        this.orderId = orderId;
        this.orderAddr = orderAddr;
        this.orderNotes = orderNotes;
        this.orderComments = orderComments;
        this.userId = userId;
        this.goodsId = goodsId;
    }
    //静态builder创建
    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private String orderId;
        private String orderAddr;
        private String orderNotes;
        private String orderComments;
        private String userId;
        private String goodsId;

        //属性名相同的静态方法
        public Builder orderId(String orderId) {
            this.orderId = orderId;
            return this;
        }

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

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


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


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


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

        public Order build() {
            return new Order(this.orderId, this.orderAddr, this.orderNotes, this.orderComments, this.userId, this.goodsId);
        }

    }
}

使用的时候

        Order order=Order.builder()
                .orderId("pcm001")
                .orderAddr("上海市黄浦区陆家嘴")
                .orderNotes("请尽快送达")
                .orderComments("五星好评")
                .userId("user001")
                .goodsId("goods001")
                .build();

原来的赋值方式

Order order=new Order("pcm001","上海市黄浦区陆家嘴","请尽快送达","五星好评","user001","goods001");

对比下来,使用构造者模式有如下好处:

  • 无需关心属性的赋值顺序
  • 可以按照具体的需要单个设置属性值
  • 赋值方法和属性名相同,不易出错
  • 代码更简洁,富有美感

有了构造者模式,妈妈再也不用担心我的赋值

2.3.2 标准建造者模式

前面通过Order的例子介绍了构造模式的变种,下面介绍标准构造者模式。标准构造者模式包含以下组件:

  • 产品类
  • 抽象Builder
  • 具体Builder
  • Director 统一构建过程
  • client

产品类-Phone

public class Phone {
    private String os;
    private String name;
    private String display;

    public String getOs() {
        return os;
    }

    public void setOs(String os) {
        this.os = os;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDisplay() {
        return display;
    }

    public void setDisplay(String display) {
        this.display = display;
    }

    @Override
    public String toString() {
        return "Phone{" +
                "os='" + os + '\'' +
                ", name='" + name + '\'' +
                ", display='" + display + '\'' +
                '}';
    }
}

抽象Builder

public  abstract class AbstractPhoneBuilder  {
   abstract void buildName();
   abstract void buildOs();
   abstract void buildDisplay();
   abstract Phone build();
}

具体Builder

public class IosPhoneBuilder extends AbstractPhoneBuilder{
    private Phone phone=new Phone();
    @Override
    void buildName() {
        phone.setName("iphone");
    }

    @Override
    void buildOs() {
        phone.setOs("ios");
    }

    @Override
    void buildDisplay() {
        phone.setDisplay("Retain XDR");
    }

    @Override
    Phone build() {
        return phone;
    }
}
public class AndroidPhoneBuilder  extends AbstractPhoneBuilder{
    private Phone phone=new Phone();
    @Override
    void buildName() {
        phone.setName("Android");
    }

    @Override
    void buildOs() {
        phone.setOs("Android");
    }

    @Override
    void buildDisplay() {
        phone.setDisplay("svmsung XDR");
    }

    @Override
    Phone build() {
        return phone;
    }
}

Director 类指导创建过程

public class Director  {

    public void construct(AbstractPhoneBuilder phoneBuilder){
        phoneBuilder.buildDisplay();
        phoneBuilder.buildName();
        phoneBuilder.buildOs();
    }
}

client 调用

public class Client {
    public static void main(String[] args) {
        AbstractPhoneBuilder iosPhoneBuilder=new IosPhoneBuilder();
        Director director=new Director();
        director.construct(iosPhoneBuilder);
        Phone iosPhone= iosPhoneBuilder.build();
        System.out.println(iosPhone);

        AbstractPhoneBuilder androidPhoneBuilder=new AndroidPhoneBuilder();
        director.construct(androidPhoneBuilder);
        Phone androidPhone= androidPhoneBuilder.build();
        System.out.println(androidPhone);
    }
}
Phone{os='ios', name='iphone', display='Retain XDR'}
Phone{os='Android', name='Android', display='svmsung XDR'}

可以看到在标准建造者模式里:抽象Builder负责抽象Builder的构建行为,具体Builder实现负责实现具体部件的构建行为,而Director类负责指导构建过程(具体按照什么顺序构建,构建哪些模块)

2.3.3 区别及联系

通过上面的对比,我们可以明显看到,变种模式相对于标准模式而言:

  • 放弃了Director 类
  • 将Director指导创建过程的工作交给了client
  • 同时将Builder设计产品类的内部类
  • 采用了链式调用
    通过上面的介绍,我们可以明显看到构造者模式的使用场景分离对象的创建过程

个人感觉在实际工作中,用变种构造者模式的场景要远大于标准构造者模式(标准构造者模式一般都在有默认值的时候使用)

3. 总结

本文简单介绍了建造者模式,并通过实例比对了变种建造者模式和标准建造者模式的区别。

可能有的同学会觉得建造者模式的代码繁琐,这里推荐一个小巧的插件Lombok。优点是省略冗余代码,简洁高效,缺点是协调开发情况下,必须安装lombok插件,不然会代码报错。且由于是通过编译时动态修改字节码文件来实现增强,所以在.java文件中看不到源码,不利于代码的调试。

由于技术水平所限,文章难免有不足之处,欢迎大家指出。希望每位读者都能有新的收获,我们下一篇文章见.....

推荐阅读更多精彩内容