首页 > 代码库 > 变种 Builder 模式:优雅的对象构建方式

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

读完本文你将了解到:

    • 帅气的 Builder 链式调用
    • 常见的两种构建方式
      • 常见的构建方式之一定义多个重载的构造函数
      • 常见的构建方式之二使用 setter 方法挨个构造
    • 优雅的构建方式变种 Builder 模式
    • Android Studio 中使用插件自动生成 变种 Builder 模式代码
    • 总结
    • Thanks

帅气的 Builder 链式调用

在日常开发中,经常可以看到这样的代码:

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

或者

new AlertDialog.Builder(this)
            .setTitle("hello")
            .setMessage("I‘m shixinzhang")
            .setIcon(R.drawable.bg_search_corner)
            .setCancelable(true)
            .setOnCancelListener(new DialogInterface.OnCancelListener() {
                @Override
                public void onCancel(DialogInterface dialog) {
                    //...
                }
            })
            .show();

可以看到这样链式调用看起来好整齐啊,Builder 模式早有耳闻,今天就来详细了解一下。

常见的两种构建方式

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

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

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

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

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

    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;
    }

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

这种方式的优点:简单。

看起来很简单嘛,每个构造函数都需要啥参数,第几个参数是干什么的,看一下就知道了,嗯,很直观!

等等!这是你站在开发者的角度的结果!

使用者使用时可得仔细了解你每个构造函数,否则一不小心填错顺序也不知道。

而且如果有十几个属性,我靠,你见过有十几个参数的构造函数吗?

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

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

吸取上面的教训,我不在构造方法里穿参数了,改成用 set 方法挨个构造,可以了吧。

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

    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;
    }

    public String getHabit() {
        return mHabit;
    }

    public void setHabit(String habit) {
        mHabit = habit;
    }
}

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

缺点是:

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

而且使用起来也不好看:

    PersonTwo personTwo = new PersonTwo("shixin");
    personTwo.setJob("洗剪吹");
    personTwo.setLocation("温州");
    personTwo.setHabit("嘿嘿嘿");

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

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

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

什么意思呢?

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

优雅的构建方式:变种 Builder 模式

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

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

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

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

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

        /**
         * 含必选参数的构造方法
         * @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;
        }

        public Builder setHabit(String habit) {
            mHabit = habit;
            return this;
        }

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

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

  • 在要构建的类内部创建一个静态内部类 Builder
  • 静态内部类的参数与构建类一致
  • 构建类的构造参数是 静态内部类,使用静态内部类的变量一一赋值给构建类
  • 静态内部类提供参数的 setter 方法,并且返回值是当前 Builder 对象
  • 最终提供一个 build 方法构建一个构建类的对象,参数是当前 Builder 对象

调用代码:

    new PersonThree.Builder("shixinzhang")
            .setLocation("Shanghai")
            .setJob("Android Develop")
            .setHabit("LOL")
            .build();

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

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

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

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

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

不过还好,有前辈开发出了 Android Studio 插件来拯救我们。

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

第一步:下载插件 Inner Builder:

技术分享

第二步:重启 Andriod Studio;

第三步:写好要构建的类的变量:

比如:

public class PersonTest {
    private final String mName;
    private int mAge;
    private String mLocation;

}

第四步:按 Control + Insert (Mac :command + N):

技术分享

第五步:在弹出的 Generate 对话框中选择 Builder:

技术分享

第六步:选中要使用 Builder 构建的对象,然后勾选使用的配置,点击 OK :

public class PersonTest {
    private final String mName;
    private int mAge;
    private String mLocation;

    private PersonTest(Builder builder) {
        mName = builder.mName;
        mAge = builder.mAge;
        mLocation = builder.mLocation;
    }

    public static final class Builder {
        private String mName;
        private int mAge;
        private String mLocation;

        public Builder() {
        }

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

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

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

        public PersonTest build() {
            return new PersonTest(this);
        }
    }
}

基本上和我们手写的一致,是不是方便很多呢?

总结

经典的 Builder 模式定义为:

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

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

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

Thanks

《Android 高级进阶》这本书的目录值得一看

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

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