建造者模式:
建造模式可以將一個產品的內部表象與產品的生成過程分割開來,從而可以使一個建造過程生成具有不同的內部表象的產品對象。當構造方法參數過多時使用建造者模式。
將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示,通俗的說就是:建造者模式就是如何一步步構建一個包含多個組成部件的對象,相同的構建過程可以創建不同的產品。
1. 簡單的構造模式
UML類圖:
具體代碼實現:
被建造的對象-----Computer.java
/**
* 產品類--被建造的對象
*/
public class Computer {
private String cpu ; // cpu
private String hardDisk ; //硬盤
private String mainBoard ; // 主板
private String memory ; // 內存
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getHardDisk() {
return hardDisk;
}
public void setHardDisk(String hardDisk) {
this.hardDisk = hardDisk;
}
public String getMainBoard() {
return mainBoard;
}
public void setMainBoard(String mainBoard) {
this.mainBoard = mainBoard;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
}
抽象的建造者-----Builder.java
/**
* 抽象的建造者,即裝電腦的步驟
* 至於安裝什麼型號的主板,不是我關心,而是具體的建造者關心的
*/
public interface Builder {
// 安裝主板
void createMainBoard(String mainBoard) ;
// 安裝 cpu
void createCpu(String cpu) ;
// 安裝硬盤
void createhardDisk(String hardDisk) ;
// 安裝內存
void createMemory(String memory) ;
// 組成電腦
Computer createComputer() ;
}
具體建造者 -----AssemblerBuilder.java
public class AssemblerBuilder implements Builder {
Computer computer = new Computer();
@Override
public void createMainBoard(String mainBoard) {
computer.setMainBoard(mainBoard);
}
@Override
public void createCpu(String cpu) {
computer.setCpu(cpu);
}
@Override
public void createhardDisk(String hardDisk) {
computer.setHardDisk(hardDisk);
}
@Override
public void createMemory(String memory) {
computer.setMemory(memory);
}
@Override
public Computer createComputer() {
return computer;
}
}
/**
* 聲明一個導演類「指揮者,這裏可以裝電腦的老闆」,用來指揮組裝過程,也就是組裝電腦的流程
*/
public class Director {
private Builder builder;
// 使用多態,裝機工很多,老闆可以指揮各個裝幾個
public Director(Builder builder){
this.builder = builder;
}
// 老闆最後只想獲得裝成的成品
public Computer createComputer(String cpu,String hardDisk,String mainBoard,String memory){
// 具體工作是裝機工去做
this.builder.createMainBoard(mainBoard);
this.builder.createCpu(cpu);
this.builder.createMemory(memory);
this.builder.createhardDisk(hardDisk);
return this.builder.createComputer();
}
}
複製代碼
測試:
public class Test {
public static void main(String[] args) {
// 裝機員小美
Builder builder = new AssemblerBuilder();
// 老闆把小明的需求轉給小美
Director director = new Director(builder);
// 老闆最後拿到成品電腦,工作小美去做
Computer computer = director.createComputer("Intel 酷睿i9 7900X","三星M9T 2TB (HN-M201RAD)","技嘉AORUS Z270X-Gaming 7","科賦Cras II 紅燈 16GB DDR4 3000");
System.out.println("小明使用的電腦是:\n"+computer.getMainBoard()+"主板\n"+computer.getCpu()+"CPU\n"+computer.getHardDisk()+"硬盤\n"+computer.getMemory()+"內存\n");
}
}
建造者模式的優缺點
- 優點
- 使創建產品的步驟「把創建產品步驟放在不同的方法中(AssemblerBuilder.java中的各個方法),更加清晰直觀」和產品本身分離,即使用相同的創建過程創建出不同的產品。
- 每個建造者都是獨立的互不影響,這樣就達到解耦的目的,所以如果想要替換現有的建造者那非常方便,添加一個實現即可。
- 缺點
- 只適用於產品具有相同的特點「過程和步驟」,如果產品之間差異非常大,則不適用「使用範圍受限」
- 萬一那天產品內部發生改變,那多個建造者都要修改,成本太大
建造者模式的使用場景
- 如果一個對象有非常複雜的內部結構「這些產品通常有很多屬性」,那麼使用建造者模式
- 如果想把複雜對象的創建和使用分離開來,那麼使用建造者模式「使用相同的創建步驟可以創建不同的產品」
建造者模式的另一種寫法:
當我看到建造者模式時,第一個想到的就是lombok的@builder的註解。
由於是用Builder模式來創建某個對象,因此就沒有必要再定義一個Builder接口,直接提供一個具體的建造者類就可以了。
對於創建一個複雜的對象,可能會有很多種不同的選擇和步驟,乾脆去掉“導演者”,把導演者的功能和Client的功能合併起來,也就是說,Client這個時候就相當於導演者,它來指導構建器類去構建需要的複雜對象。
先看下面這個例子該如何處理:
public class User{
String name;
int age;
String email;
String address;
public User(){
}
//想要有名字和郵箱的構造器
public User(String name, String email){
this.name = name;
this.email = email;
}
//想要有名字和地址的構造器
public User(String name, String address){
this.name = name;
this.address = address;
}
}
在上面代碼中,很容易可以發現,在我們正常的需求下,Java構造器的編寫將會出問題,由於參數個數和類型一樣無法構成重載,所以這樣寫是不行的,那麼我們可以通過Builder模式解決這種情況。
public class User {
String name;
int age;
String phone;
String email;
String address;
//注意無參構造器私有,避免外界使用構造器創建User對象
private User() {
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + ", phone=" + phone + ",
email=" + email + ", address=" + address
+ "]";
}
public static class Builder {
private String name;
private int age;
private String phone;
private String email;
private String address;
public Builder() {
}
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setAge(int age) {
this.age = age;
return this;
}
public Builder setPhone(String phone) {
this.phone = phone;
return this;
}
public Builder setEmail(String email) {
this.email = email;
return this;
}
public Builder setAddress(String address) {
this.address = address;
return this;
}
public User build() {
User user = new User();
user.name = name;
user.age = age;
user.email = email;
user.phone = phone;
user.address = address;
return user;
}
}
}
根據上面的代碼,我們可以看出來,就是在User內部創建一個內部類,並且擁有和User一樣的字段(屬性),並且提供SET方法,最重要的是要提供一個能夠返回User對象的方法(build),這樣才能通過Builder來創建User對象。
Builder設計模式還有一個好處,那便是我們可以隨意組合輸入的參數,不僅避免了重載出錯的問題,還不需要寫過多的構造器。
下面我們一起看看寫完Builder模式類之後如何來調用:
public class UserTest {
public static void main(String[] args) {
User u = new User.Builder().setName("bob").setAge(22).build();
System.out.println(u);
}
}
看到這個寫法是不是很熟悉,和我們使用lombok中得@builder 註解構造對象的方式是一樣的。我們看一下lombok中@builder反編譯後的代碼
@Builder
public class User {
private final Integer code = 200;
private String username;
private String password;
}
// 編譯後:
public class User {
private String username;
private String password;
User(String username, String password) {
this.username = username; this.password = password;
}
public static User.UserBuilder builder() {
return new User.UserBuilder();
}
public static class UserBuilder {
private String username;
private String password;
UserBuilder() {}
public User.UserBuilder username(String username) {
this.username = username;
return this;
}
public User.UserBuilder password(String password) {
this.password = password;
return this;
}
public User build() {
return new User(this.username, this.password);
}
public String toString() {
return "User.UserBuilder(username=" + this.username + ", password=" + this.password + ")";
}
}
}
使用builder模式的好處:
1. 通過構造函數傳參的方式來進行構建(臃腫 不能靈活地只設置某些參數)
2.寫很多構造方法。而且容易吧兩個屬性的參數寫錯位置等等;(重載很多的構造器,然後可以保證數據的一致性,比較安全)
3.寫一堆setter方法,缺點就是夠早的過程會被分到幾個調用中。構造可以出現不一致的狀態。(而且代碼很囉嗦)
4.採用builder模式的話,當一個類的參數很多的時候。
(使用javabean模式,調用一個無參的構造器,然後調用setter方法來設置每個必要的參數。但是javabean自身有着嚴重的缺點,因爲構造過程被分到幾個調用中,在構造javabean可能處於不一致的狀態,類無法僅僅通過檢驗構造器參數的有效性來保證一致性。也就是set的過程中其實很多時候並不能保證一致性,因爲set已經算是分開執行了)
build模式 既能保證像重疊構造器那樣的安全,也能實現JavaBean模式那樣的可讀性。 (不用操心線程)