Java-设计模式:建造者模式

161 阅读4分钟

引言

在Android开发中,你或许看到过以下初始化对象的方法.

比如新建Retrofit对象的时候:

Retrofit.Builder()
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .client(OkHttpClient())
                .baseUrl("https://juejin.cn")
                .build()

又比如你新建一个AlertDialog的时候:

AlertDialog.Builder(this)
                .setMessage("hello juejin")
                .setView(View(this))
                .setIcon(R.drawable.ic_dialog)
                .create()

这种写法让我着迷的地方在于它的链式调用,给人一种写代码就像写诗一样的感觉,而且这种写法对对象属性的初始化也不分顺序,只要在调用builder()之前依次调用即可,很是舒服。

而这种写法就是使用的设计模式中的建造者模式,也称Builder,模式.

优势以及实现

在未使用建造者模式之前,我们是怎么初始化对象并赋值属性的呢?举个栗子,对于一个人,我们假设他有这些属性:

public class Person {
    private int name;//姓名属性
    private int age; //年龄属性
    private String gender;//性别属性
    private float height; //身高属性
    private float weight; //体重属性
    private String idCard;//身份证号码
    
    .....
    省略的`getter`和`setter`方法
}

如果我们需要Person在被实例化的时候属性得到赋值,我们可能会这样做:

public class Person {
    private int name;//姓名属性
    private int age; //年龄属性
    private String gender;//性别属性
    private float height; //身高属性
    private float weight; //体重属性
    private String idCard;//

    //仅仅在构造函数中赋值 姓名和年龄属性
    public Person(int name, int age) {
        this.name = name;
        this.age = age;
    }
    //在构造函数中赋值 姓名,年龄,和性别属性
    public Person(int name, int age, String gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    //在构造函数中赋值 姓名,年龄,性别,身高,体重属性
    public Person(int name, int age, String gender, float height, float weight) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.height = height;
        this.weight = weight;
    }
    //在构造函数中赋值 姓名,年龄,性别,身高,体重,身份证属性
    public Person(int name, int age, String gender, float height, float weight, String idCard) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.height = height;
        this.weight = weight;
        this.idCard = idCard;
    }
    
       .....
    省略的`getter`和`setter`方法
}

我们利用不同构造函数的多个重载达到不同程度的属性赋值效果,这种方法我们称之为telescoping constructor(重叠构造器)模式.对于这种模式,固然可以达到效果,但是无论是从代码的编写和可读上面来看,都是很糟糕的,因此,对于这种属性繁多,又需要在初始化的时候赋值的情况,我们便可以使用Builder模式.试着来改造上面的栗子.

public class Person {
    private String name;//姓名属性
    private int age; //年龄属性
    private String gender;//性别属性
    private float height; //身高属性
    private float weight; //体重属性
    private String idCard;//身份证号码

    //需要通过builder进行构建的构造函数,在构造函数中,将builder中的属性一一赋值给person
    private Person(Builder builder) {
        name = builder.name;
        age = builder.age;
        gender = builder.gender;
        height = builder.height;
        weight = builder.weight;
        idCard = builder.idCard;
    }
    //不需要通过builder进行构建的构造函数,
    public Person() {
    }
    //采用静态内部类的原因是静态内部类可独立于外部类进行实例
    static class Builder {
        private String name;//姓名属性
        private int age; //年龄属性
        private String gender;//性别属性
        private float height; //身高属性
        private float weight; //体重属性
        private String idCard;//身份证号码
        
        //build()方法,在属性设置完毕后调用,在内部实例person,并传入自身
        public Person build() {
            return new Person(this);
        }
        //set方法,注意,设置值之后返回自身,才能达成链式调用
        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setAge(int age) {
            this.age = age;
            return this;
        }

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

        public Builder setHeight(float height) {
            this.height = height;
            return this;
        }

        public Builder setWeight(float weight) {
            this.weight = weight;
            return this;
        }

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

    //toString()方法,方便打印
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Person:")
                .append("\n")
                .append("name:")
                .append(name)
                .append("\n")
                .append("age:")
                .append(age)
                .append("\n")
                .append("gender:")
                .append(gender)
                .append("\n")
                .append("height:")
                .append(height)
                .append("\n")
                .append("weight:")
                .append(weight)
                .append("\n")
                .append("idCard:")
                .append(idCard);
        return sb.toString();
    }
    
        .....
    省略的`getter`和`setter`方法
}
   

上面的代码就已经改造成功啦。总结下改造的方式:

  • 在需要使用Builder模式的类中,建立静态内部类Builder.
  • 增加带一个参数类型为自身内部Builder的构造函数,并将Builder中所有属性赋值给自己.
  • Builder中有外部内全部属性,并有对应set方法,set方法的返回值为自身.
  • Builder中,builder()负责实例化外部类,并传入自身。

测试代码:

  public static void main(String[] args) {
        Person person = new Person.Builder()
                .setName("cs丶")
                .setAge(18)
                .setGender("Man")
                .setHeight(185)
                .setWeight(68)
                .setIdCard("10086")
                .build();
        System.out.println(person);
    }

打印的结果:

Person:
name:cs丶
age:18
gender:Manheight:185.0
weight:68.0
idCard:10086

总结

Builder模式虽好,可不要贪杯哦,对于属性较少的类,还是老老实实用构造函数初始化就行了。