特點
將一個複雜對象的構建過程封裝起來,使得同樣的構建過程可以創造不同的表示,簡單講就是
把複雜的都包成api
舉例
反例
Person
裡面包括多個屬性,但如果其中有幾個參數是非必填,則
- 缺乏彈性:如果Person的某些屬性是可選的,使用這種重載構造函數的方式會導致必須創建多個構造函數來支持不同的參數組合。
- 可讀性差:隨著參數數量的增加,調用這些重載構造函數的代碼變得難以理解,特別是當多個參數具有相同的類型時。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Person { private String name; private int age; private String phoneNumber; private String address;
public Person(String name, int age, String phoneNumber, String address) { this.name = name; this.age = age; this.phoneNumber = phoneNumber; this.address = address; }
}
|
修正
定義builder
將person
的constructor封在 PersonBuilder
內
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class PersonBuilder { private String name; private int age; private String phoneNumber; private String address;
public PersonBuilder setName(String name) { this.name = name; return this; }
public PersonBuilder setAge(int age) { this.age = age; return this; }
public PersonBuilder setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; return this; }
public PersonBuilder setAddress(String address) { this.address = address; return this; }
public Person build() { return new Person(name, age, phoneNumber, address); } }
|
修改person
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Person { private String name; private int age; private String phoneNumber; private String address;
private Person(String name, int age, String phoneNumber, String address) { this.name = name; this.age = age; this.phoneNumber = phoneNumber; this.address = address; }
@Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", phoneNumber='" + phoneNumber + '\'' + ", address='" + address + '\'' + '}'; }
public static PersonBuilder builder() { return new PersonBuilder(); } }
|
使用Builder模式創建Person
1 2 3 4 5 6 7 8 9 10 11 12
| public class Main { public static void main(String[] args) { Person person = Person.builder() .setName("John Doe") .setAge(30) .setPhoneNumber("123456789") .setAddress("123 Main St") .build();
System.out.println(person.toString()); } }
|
1
| Person{name='John Doe', age=30, phoneNumber='123456789', address='123 Main St'}
|
優點
1. 提高可讀性
當創建擁有多個屬性的對象時,直接使用包含多個參數的構造函數會讓代碼難以閱讀和理解。建造者模式通過命名方法(即setter方法)逐一設置屬性,使得代碼的可讀性大大提高,一目了然地顯示了正在設置的屬性及其值。
2. 簡化對象的創建
對於具有多個構造參數的類,特別是當某些參數可選時,如果使用重載構造函數來實現,會導致構造函數的爆炸性增長。建造者模式允許用戶只設置對他們有意義的參數,並且可以在單一位置進行設置,避免了重載構造函數或多個setter調用的混亂。
3. 促進不可變性
對於需要創建不可變對象的情況,建造者模式非常有用。一旦通過建造者配置了對象的所有必要屬性,就可以創建一個不可變的對象實例,而無需對對象實例進行進一步修改。這有助於保證線程安全性和對象的一致性。
4. 支持鏈式調用
建造者模式支持鏈式調用,這使得代碼更加簡潔,也更易於編寫和理解。鏈式調用提供了一種流暢的接口,讓客戶端代碼在一行或少數幾行內完成複雜對象的構建。
5. 分離複雜構建過程和表示
建造者模式將對象的構建過程與其表示分離,使得相同的構建過程可以創建不同的表示。這對於創建具有多種表示形式的對象特別有用,如文本、XML、JSON等不同格式的對象序列化。
6. 靈活性和擴展性
建造者模式使得新增或更改對象的屬性變得更加容易,只需修改建造者本身而不影響到創建對象的客戶端代碼。這為擴展和維護提供了極大的方便。
Fluent Builder
Fluent Builder 與一般的 Builder 模式類似,但在語法風格上更加「流暢(Fluent)」。所謂的 Fluent Interface 是透過方法回傳 this 物件本身,讓我們能以類似鏈式呼叫(chained method calls)的方式設定屬性。
範例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| public class FluentPerson { private final String firstName; private final String lastName; private final int age; private final String phone; private final String address;
private FluentPerson(PersonBuilder builder) { this.firstName = builder.firstName; this.lastName = builder.lastName; this.age = builder.age; this.phone = builder.phone; this.address = builder.address; }
public static PersonBuilder named(String firstName, String lastName) { return new PersonBuilder(firstName, lastName); }
public static class PersonBuilder { private final String firstName; private final String lastName; private int age; private String phone; private String address;
private PersonBuilder(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }
public PersonBuilder whoIs(int age) { this.age = age; return this; }
public PersonBuilder withPhone(String phone) { this.phone = phone; return this; }
public PersonBuilder livingAt(String address) { this.address = address; return this; }
public FluentPerson build() { return new FluentPerson(this); } }
@Override public String toString() { return String.format("FluentPerson [firstName=%s, lastName=%s, age=%d, phone=%s, address=%s]", firstName, lastName, age, phone, address); } }
public class MainFluent { public static void main(String[] args) { FluentPerson person = FluentPerson .named("Jane", "Smith") .whoIs(28) .withPhone("987654321") .livingAt("Another Street 456") .build();
System.out.println(person); } }
|