1. 程式人生 > >構建器(Builder)模式

構建器(Builder)模式

一、引言

在日常的開發中,我們可能經常能看到如下的程式碼:

PrivateKeyDecryptParam param = new PrivateKeyDecryptParam.Builder()
                                           .uAppId(uAppId)
                                           .containerId(containerId)
                                           .cipher(cipher)
                                           .signFlag(signFlag)
                                           .build();

在Android中,也會看到下面建立AlertDialog程式碼:

new AlertDialog.Builder(this)
        .setTitle("標題")
        .setMessage("內容")
        .setCancelable(true)
        .setOnCancelListener(new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
             
            }
        })
        .show();

觀察上面這兩段程式碼,都有一個共同的特點,就是都可以進行鏈式操作,這就是我們要學習的Builder模式,下面就來詳細學習一下。

二、Builer模式的使用場景

在《Effective Java第2版》書中有提到,當遇到多個構造器引數時,要考慮使用構建器(Builder模式)。

舉個例子,比如在專案中,我們需要新建一個Person類,假設該類有7個屬性(現實中遠不止這幾個引數),其中有2個是必要的引數需要初始化,分別是id和name。

1、使用JavaBean的setter方法來設定物件屬性

最常見的寫法應該是寫成JavaBean。程式碼如下:

public class Person {
    //必要引數
    private int id;
    private String name;
    //可選引數
    private int age;
    private String sex;
    private String phone;
    private String address;
    private String desc;

    public void setId(int id) {
        this.id = id;
    }

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

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

    public void setSex(String sex) {
        this.sex = sex;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}

在使用該Person類的時候,會寫出如下的程式碼:

Person person = new Person();
person.setId(1);
person.setName("張小毛");
person.setAge(22);
person.setSex("男");
person.setPhone("19999999999");
person.setAddress("beijing");
person.setDesc("JavaBeans模式");

這種JavaBean的方式也是常見的構造物件並賦值的方式,這種方式的好處是:

(1)、易於閱讀,並且可以只對有用的成員變數賦值;

但它的缺點是:

(1)、成員變數不可以是 final 型別,失去了不可變物件的很多好處;

(2)、物件狀態不連續,你必須呼叫7次setter方法才能得到一個具備7個屬性值得變數,在這期間使用者可能拿到不完整狀態的物件。如果有N個屬性,豈不是要person.setXXX呼叫N次?此種方式不優雅。

最重要的缺點是第二條:物件狀態不連續。什麼意思呢?

解釋一下:這種方式是 先建立物件、後賦值,使用者不知道什麼時候拿到的物件是完整的,構建完成的。很有可能你只setter了一兩個屬性就返回了,一些必要的屬性沒有被賦值。

2、使用重疊構造器

在這種模式下,需要提供一個只有必要引數的構造器,第二個構造器有一個可選引數,第三個有兩個可選引數,依此類推,最後一個構造器包含所有的可選引數。程式碼如下:

public class Person {
    //必要引數
    private final int id;
    private final String name;
    //可選引數
    private int age;
    private String sex;
    private String phone;
    private String address;
    private String desc;

    public Person(int id, String name) {
        this(id, name, 0);
    }

    public Person(int id, String name, int age) {
        this(id, name, age, "");
    }

    public Person(int id, String name, int age, String sex) {
        this(id, name, age, sex, "");
    }

    public Person(int id, String name, int age, String sex, String phone) {
        this(id, name, age, sex, phone, "");
    }

    public Person(int id, String name, int age, String sex, String phone, String address) {
        this(id, name, age, sex, phone, address, "");
    }

    public Person(int id, String name, int age, String sex, String phone, String address, String desc) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.phone = phone;
        this.address = address;
        this.desc = desc;
    }
}

從上面的程式碼可以看出,當你想要建立例項的時候,就利用引數列表最短的構造器,但該列表中包含了要設定的所有引數,其餘都為預設值:

Person person = new Persion(1, "張小毛");

這個構造器呼叫,通常需要許多你本不想設定的引數,但還是不得不為它們傳遞值。

這種方式的優點就是:簡單!!!!(這是對開發者而言),但使用者在使用時,可得仔細瞭解你每個建構函式,否則一不小心填錯順序也不知道。而且如果有十幾個屬性,就歇菜了……(我也沒見過有十幾個引數的建構函式)

所以缺點是:

只適用於成員變數少的情況,太多了不容易理解、維護。

簡而言之:重疊構造器可行,但是當有許多引數的時候,建立使用程式碼會很難寫,並且較難以閱讀。

三、變種Builder模式

對於上述分析的兩種方法,都存在優點和缺點,為了解決上述兩種構建方式的不足,偉大的程式設計師們創造出了變種 Builder模式。直接看程式碼:

public class Person {
    //必要引數
    private final int id;
    private final String name;
    //可選引數
    private int age;
    private String sex;
    private String phone;
    private String address;
    private String desc;

    private Person(Builder builder) {
        this.id = builder.id;
        this.name = builder.name;
        this.age = builder.age;
        this.sex = builder.sex;
        this.phone = builder.phone;
        this.address = builder.address;
        this.desc = builder.desc;
    }

    public static class Builder {
        //必要引數
        private final int id;
        private final String name;
        //可選引數
        private int age;
        private String sex;
        private String phone;
        private String address;
        private String desc;

        public Builder(int id, String name) {
            this.id = id;
            this.name = name;
        }

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

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

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

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

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

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

觀察上述程式碼,可以看到變種Builder模式包括以下內容:

(1)、在要構建類的內部,建立一個靜態內部類Builder;

(2)、靜態內部類的屬性要與構建類的屬性一致;

(3)、構建類的構造引數是靜態內部類,使用靜態內部類的變數為構建類逐一賦值;

(4)、靜態內部類提供引數的setter方法,並且返回值是當前Builder物件;

(5)、最終提供一個build方法new出來一個構建類的物件,引數是當前Builder物件;

呼叫程式碼如下:

public class Test {

    public static void main(String[] args) {
        Person person = new Person.Builder(1, "張小毛")
                .age(22).sex("男").desc("使用builder模式").build();
        System.out.println(person.toString());
    }
}

對變種Builer模式的總結:

(1)、變種Builder模式目的在於:減少物件建立過程中引入的多個建構函式、可選引數以及多個setter過度使用導致的不必要的複雜性。

(2)、優點就是:看起來很整齊;先賦值,後建立物件,最終呼叫build()方法才建立了構建類的物件,保證了狀態的完整性。

(3)、缺點嘛,就是需要額外寫的程式碼多了點。

四、總結:

變種Builer模式相比於重疊構造器模式和JavaBeans模式,Builder模式實現的物件更利於使用。

對Builer模式使用方法的總結:
(1)、外部類的建構函式私有,且引數為靜態內部類;
(2)、靜態內部類擁有外部類相同的屬性;
(3)、為每一個屬性,寫一個方法,返回的是Builer;
(4)、最後一個方法是build方法,用於構建一個外部類;