設計模式-4.建造者模式

建造者模式:

  建造模式可以將一個產品的內部表象與產品的生成過程分割開來,從而可以使一個建造過程生成具有不同的內部表象的產品對象。當構造方法參數過多時使用建造者模式。

將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示,通俗的說就是:建造者模式就是如何一步步構建一個包含多個組成部件的對象,相同的構建過程可以創建不同的產品。

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

 

建造者模式的優缺點

  • 優點
    1. 使創建產品的步驟「把創建產品步驟放在不同的方法中(AssemblerBuilder.java中的各個方法),更加清晰直觀」和產品本身分離,即使用相同的創建過程創建出不同的產品。
    2. 每個建造者都是獨立的互不影響,這樣就達到解耦的目的,所以如果想要替換現有的建造者那非常方便,添加一個實現即可。
  • 缺點
    1. 只適用於產品具有相同的特點「過程和步驟」,如果產品之間差異非常大,則不適用「使用範圍受限」
    2. 萬一那天產品內部發生改變,那多個建造者都要修改,成本太大

建造者模式的使用場景

  1. 如果一個對象有非常複雜的內部結構「這些產品通常有很多屬性」,那麼使用建造者模式
  2. 如果想把複雜對象的創建和使用分離開來,那麼使用建造者模式「使用相同的創建步驟可以創建不同的產品」

 

 建造者模式的另一種寫法:

當我看到建造者模式時,第一個想到的就是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模式那樣的可讀性。 (不用操心線程)
 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章