多参构造器与Builder模式及链式调用的实际运用

1. 前言:

buider 第一反应是构造者模式的运用,其实也可以用于对象的构造以及优雅的链式调用。

首先,回顾标准的用法

在《Effective Java 第2版》中有提到,遇到多个构造器参数时要考虑使用构建器(Builder模式)。相比于重叠构造器(telescoping constructor)模式JavaBeans模式Builder模式实现的对象更利于使用。


/**
 * 
 *     author : June Yang
 *     time   : 2021/02/06
 *     desc   :
 *     version: 1.0
 * 
*/ public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder { //必须注入的参数,可不用初始化 private final int servingSize; private final int servings; //可选择注入的参数 private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder setCalories(int calories) { this.calories = calories; return this; } public Builder calories(int val) { this.calories = val; return this; } public Builder carbohydrate(int val) { this.carbohydrate = val; return this; } public Builder sodium(int val) { this.sodium = val; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } @Override public String toString() { return "NutritionFacts{" + "servingSize=" + servingSize + ", servings=" + servings + ", calories=" + calories + ", fat=" + fat + ", sodium=" + sodium + ", carbohydrate=" + carbohydrate + '}'; } //私有构造方法 private NutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; } public static void main(String[] args) { NutritionFacts bean = new NutritionFacts. Builder(8, 20) .calories(30) .build(); } }

调用时:

   public static void main(String[] args) {
        NutritionFacts bean = new NutritionFacts.
                Builder(8, 20)
                .calories(30)
                .build();
    }

2. Android项目的中运用

Android开发中经常遇到这样的代码:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.xxx.com/")
    .build();

或者

new AlertDialog.Builder(this)
            .setTitle("TitleName")
            .setMessage("Message")
            .setCancelable(true)
            .setOnCancelListener(new DialogInterface.OnCancelListener() {
                @Override
                public void onCancel(DialogInterface dialog) {
                    //...
                }
            })
            .show();

builder实现的链式调用看上去如此优雅。于是项目开发中我们也想实际用起来。

3. 常见的两种构建方式

在日常开发中,我们经常需要给某个对象的变量赋值,这个赋值的过程称为 对象的构建。

比如现在有个 Person 类,它有几个成员变量:

//固定不变的对象,一般变量需要声明为 final
private final String mName;     //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在
private String mLocation;       //可选
private String mJob;            //可选

3.1 常见的构建方式之一:定义多个重载的构造函数

public class PersonOne {
    //固定不变的对象,一般变量需要声明为 final
    private final String mName;     //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在
    private String mLocation;       //可选
    private String mJob;            //可选

    public PersonOne(String name) {
        mName = name;
    }

    public PersonOne(String location, String name) {
        mLocation = location;
        mName = name;
    }

    public PersonOne(String name, String location, String job) {
        mName = name;
        mLocation = location;
        mJob = job;
    }
}

这种方式的优点:简单。

所以缺点是
只适用于成员变量少的情况,太多了不容易理解、维护。不优雅。

3.2 常见的构建方式之二:使用 setter 方法挨个构造

public class PersonTwo {
    //固定不变的对象,一般变量需要声明为 final
    private final String mName;     //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在
    private String mLocation;       //可选
    private String mJob;            //可选

    public PersonTwo(String s) {
         this.mName = s;
    }

    public String getName() {
        return mName;
    }

    public String getLocation() {
        return mLocation;
    }

    public void setLocation(String location) {
        mLocation = location;
    }

    public String getJob() {
        return mJob;
    }

    public void setJob(String job) {
        mJob = job;
    }
}

这种方式也是常见的构造方式,它的好处是:易于阅读,并且可以只对有用的成员变量赋值;

缺点是:

  • 成员变量不可以是 final 类型,失去了不可变对象的很多好处;
  • 对象状态不连续,你必须调用 4 次 setter 方法才能得到一个具备 4 个属性值得变量,在这期间用户可能拿到不完整状态的对象。

而且使用起来也不好看:

   PersonTwo personTwo = new PersonTwo("shixin");
    personTwo.setJob("Android");
    personTwo.setLocation("成都");

如果有 N 个属性岂不是要 personTwo.setXXX N 回?不优雅!

即使把 setXXX 方法返回值改成当前构造类,但还是不满足最重要的缺点的第二点:

用户可能拿到不完整状态的对象。

什么意思呢?

这种方式是 先创建对象、后赋值,用户不知道什么时候拿到的对象是完整的,构建完成的。很有可能你只 set 了一两个属性就返回了,一些必要的属性没有被赋值。

3.3 优雅的构建方式:变种 Builder 模式(回到标准用法上)

为了解决上述两种构建方式,伟大的程序员们创造出了 变种 Builder 模式

先来看看用 变种 Builder 模式怎么实现上述 Person 对象的构建吧:

public class PersonThree {
    //固定不变的对象,一般变量需要声明为 final
    private final String mName;     //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在
    private String mLocation;       //可选
    private String mJob;            //可选

    /**
     * 构造方法的参数是它的 静态内部类,使用静态内部类的变量一一赋值
     * @param builder
     */
    public PersonThree(Builder builder) {
        this.mName = builder.mName;
        this.mLocation = builder.mLocation;
        this.mJob = builder.mJob;
    }

    /**
     * PersonTree 的静态内部类,成员变量和 PersonTree 的一致
     */
    public static class Builder{
        private final String mName;     //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在
        private String mLocation;       //可选
        private String mJob;            //可选

        /**
         * 含必选参数的构造方法
         * @param name
         */
        public Builder(String name) {
            mName = name;
        }

        public Builder setLocation(String location) {
            mLocation = location;
            return this;
        }

        public Builder setJob(String job) {
            mJob = job;
            return this;
        }

        /**
         * 最终构建方法,返回一个 PersonTree 对象,参数是当前 Builder 对象
         * @return
         */
        public PersonThree build(){
            return new PersonThree(this);
        }
    }
}

可以看到,变种 Builder 模式包括以下内容:

  • 在要构建的类内部创建一个静态内部类 Builder
  • 静态内部类的参数与构建类一致
  • 构建类的构造参数是 静态内部类,使用静态内部类的变量一一赋值给构建类
  • 静态内部类提供参数的 setter 方法,并且返回值是当前 Builder 对象
  • 最终提供一个 build 方法构建一个构建类的对象,参数是当前 Builder 对象
  new PersonThree.Builder("xiaoyangzishuo")
            .setLocation("ChengDu ")
            .setJob("Android Develop")
            .build();

变种 Builder 模式目的在于减少对象创建过程中引入的多个构造函数、可选参数以及多个 setter 过度使用导致的不必要的复杂性。

好处就是文章开头所说的:

  • 看起来很整齐;
  • 先赋值,后创建对象。

最终调用 build() 方法才创建了构建类的对象,保证了状态的完整性。

缺点嘛,就是需要额外写的代码多了点。

3.4 Android Studio 中使用插件自动生成 变种 Builder 模式代码

下载插件 Inner Builder 也可以快速实现Builder的构建,代码同上。略。

4. 链式调用(其他builder的一种变种方式)

class Calculator {
    private int total;

    public Calculator(int cardinality) {
        this.total = cardinality;
    }

    public Calculator add(int addend) {
        this.total += addend;
        return this;
    }

    public Calculator minus(int minus) {
        this.total -= minus;
        return this;
    }

    public Calculator multiply(int multiplier) {
        this.total *= multiplier;
        return this;
    }

    public Calculator divideBy(int divisor) {
        this.total /= divisor;
        return this;
    }

    public Calculator calculate() {
        return this;
    }

    public int getTotal() {
        return total;
    }
}

将方法的返回值类型指定为当前 Class ,那么,在方法体的最后可以直接 return this ,从而可以形成一个类似于 Builder Patern 的效果。

用法如下:

Calculator calculator = new Calculator(5)
    .add(3)
    .minus(4)
    .multiply(2)
    .divideBy(4)
    .calculate(); //最后一行不要也能创建成功,好多文章都是说要添加,不要似乎也没有什么影响 不知你怎么看,求解惑。

总结

经典的 Builder 模式定义为:

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

它的重点在于:抽象出对象创建的具体步骤到一个接口,通过调用不同的接口实现,从而得到不同的结果。

Builder 模式在 Android 开发中演变出了 变种 Builder 模式,它除了具备经典构建者模式的功能,还简化了构建的过程,使得创建过程更加简单、直观。

参考:

1.《Effective Java》

2.《Effective Java》读书笔记02-多参构造器与Builder模式

2.变种 Builder 模式:优雅的对象构建方式

3. 使用Builder模式创建复杂可选参数对象

4《effective java》builder模式一些思考

5. EffectiveJava---Builder设计模式

从 Java Builder Pattern 到 return this 链式调用

Java高效编程之Builder模式

你可能感兴趣的:(多参构造器与Builder模式及链式调用的实际运用)