1. 程式人生 > >Java高效程式設計之Builder模式

Java高效程式設計之Builder模式

前言

在《Effective Java 第2版》中有提到,遇到多個構造器引數時要考慮使用構建器(Builder模式)。相比於重疊構造器(telescoping constructor)模式JavaBeans模式Builder模式實現的物件更利於使用。
下面從一個Person例子進行分析以上三種設計模式的使用,Person類有兩個必要引數(id和name),有5個可選引數(age,sex,phone,address和desc)

1 重疊構造器模式

我們先來看看程式設計師一向習慣使用的重疊構造器模式,在這種模式下,你提供第一個只有必要引數的構造器,第二個構造器有一個可選引數,第三個有兩個可選引數,依此類推,最後一個構造器包含所有的可選引數。下面看看其程式設計實現:

/**
 * 使用重疊構造器模式
 */
public class Person {
    //必要引數
    private final int id;
    private final String name;
    //可選引數
    private final int age;
    private final String sex;
    private final String phone;
    private final String address;
    private final 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, "李四", 20, "男", "18800000000", "China", "測試使用重疊構造器模式");

這個構造器呼叫通常需要許多你本不想設定的引數,但還是不得不為它們傳遞值。
一句話:重疊構造器可行,但是當有許多引數的時候,建立使用程式碼會很難寫,並且較難以閱讀。

2 JavaBeans模式

遇到許多構造器引數的時候,還有第二種代替辦法,即JavaBeans模式。在這種模式下,呼叫一個無參構造器來建立物件,然後呼叫setter辦法來設定每個必要的引數,以及每個相關的可選引數:

/**
 * 使用JavaBeans模式
 */
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 = new Person();
person.setId(1);
person.setName("李四");
person.setAge(20);
person.setSex("男");
person.setPhone("18800000000");
person.setAddress("China");
person.setDesc("測試使用JavaBeans模式");

遺憾的是,JavaBeans模式自身有著很重要的缺點。因為構造過程被分到了幾個呼叫中,在構造過程中JavaBean可能處於不一致的狀態。類無法僅僅通過檢驗構造器引數的有效性來保證一致性。

3 Builder模式(推薦)

幸運的是,還有第三種替代方法,既能保證像重疊構造器模式那樣的安全性,也能保證像JavaBeans模式那麼好的可讀性。這就是Builder模式的一種形式,不直接生成想要的物件,而是讓客戶端利用所有必要的引數呼叫構造器(或者靜態工廠),得到一個builder物件。然後客戶端在builder物件上呼叫類似於setter的方法,來設定每個相關的可選引數。最後,客戶端呼叫無參的builder方法來生成不可變的物件。這個builder是它構建類的靜態成員類。下面就是它的示例:
/**
 * 使用Builder模式
 */
public class Person {
    //必要引數
    private final int id;
    private final String name;
    //可選引數
    private final int age;
    private final String sex;
    private final String phone;
    private final String address;
    private final 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);
        }
    }
}

注意Person是不可變得,所有的預設引數值都單獨放在一個地方。builder的setter方法返回builder本身。以便可以把連線起來。下面是客戶端使用程式碼:

/**
 * 測試使用
 */
public class Test {

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

這裡寫圖片描述

這樣的客戶端程式碼很容易編寫,更為重要的是,易於閱讀。

本文是參照《Effective Java 第2版》第二章建立和銷燬物件第2條(遇到)所做的學習記錄筆記,本書裡面還有很多非常不錯的Java高效程式設計方法,有興趣的可以找來看看。