1. 程式人生 > >設計模式 #3 (原型模式、建造者模式)

設計模式 #3 (原型模式、建造者模式)

# 設計模式 #3 (原型模式、建造者模式) --- **文章中所有工程程式碼和`UML`建模檔案都在我的這個`GitHub`的公開庫--->[DesignPattern](https://github.com/L1ng14/DesignPattern)。**`Star`來一個好嗎?秋梨膏! --- ## 原型模式 簡述:用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。 #### 反例 #1 : ~~~java public class negtive { /*==============服務端=======================*/ static class Resume{ private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Resume{" + "name='" + name + '\'' + ", age=" + age + '}'; } } /*==============客戶端=========================*/ public static void main(String[] args) { Resume resume01= new Resume(); resume01.setName("ling001"); resume01.setAge(20); System.out.println(resume01); Resume resume02= new Resume(); resume02.setName("ling002"); resume02.setAge(23); System.out.println(resume02); } } ~~~ 複製多份簡歷需要一個個去`new`。咱們都是`IT`人士了,得專業點,重複無用功怎麼能做呢?程式設計師要說最熟的,難道不是`Ctrl+C`+`Ctrl+V`嗎?(手動滑稽保命)Java就提供了這種複製貼上的辦法,不過他有自己的名字--`Clone`。 #### 正例 #1: ~~~java public class postvie_01 { /*==============服務端=======================*/ static class Resume implements Cloneable{ private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Resume{" + "name='" + name + '\'' + ", age=" + age + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } /*==============客戶端=========================*/ public static void main(String[] args) throws CloneNotSupportedException { Resume resume01= new Resume(); resume01.setName("ling001"); resume01.setAge(20); Resume resume02= (Resume) resume01.clone(); resume02.setName("li666"); System.out.println(resume01); System.out.println(resume02); System.out.println(resume01.equals(resume02)); } } ~~~ 不需要`new`,只需要服務端先實現一個`Cloneable`介面,並重寫`clone`方法即可。而且作用堪比`new`一個新的物件,因為克隆和被克隆的物件並不是同一個,`equals`的時候得到的是`false`的。 這時候,新需求來了(這次沒有產品經理,別拿刀K自己):為簡歷增加一個工作經歷的內容,這時候: 反例 #2: ~~~java public class negtive_02 { /*==============服務端=======================*/ static class Resume implements Cloneable{ private String name; private Integer age; private Date workExperience; public Date getWorkExperience() { return workExperience; } public void setWorkExperience(Date workExperience) { this.workExperience = workExperience; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Resume{" + "name='" + name + '\'' + ", age=" + age + ", workExperience=" + workExperience + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } /*==============客戶端=========================*/ public static void main(String[] args) throws CloneNotSupportedException{ Resume resume01= new Resume(); Date date = new Date(); resume01.setName("ling001"); resume01.setAge(20); resume01.setWorkExperience(date); Resume resume02= (Resume) resume01.clone(); resume02.getWorkExperience().setTime(0); System.out.println(resume02); System.out.println(resume01); System.out.println(resume01.equals(resume02)); } } ~~~ ![image-20200915174612254](https://i.loli.net/2020/09/15/9ZIAOhJYfd6sDUN.png) 這是一個關於深拷貝、淺拷貝的問題。 ### 深拷貝與淺拷貝 淺拷貝(淺複製):`clone`得到的物件`a`其實只是對被`clone`物件`b`的引用,即物件`a`是指向`b`物件上的。 ![image-20200915174807631](https://i.loli.net/2020/09/15/yoMNBDwQrHCSVPv.png) 深拷貝(深複製):`clone`得到的物件`a`是對被`clone`物件`b`的複製,即物件`a`和`b`是兩個不同的物件,`a`只複製了`b`的內容。 ![image-20200915175541062](https://i.loli.net/2020/09/15/OVFq3LJTcMAhX5Z.png) `Java`中,對一物件的`clone`中**深拷貝**物件的基本型別欄位,**淺拷貝**引用型別欄位。 這時候要將淺拷貝改為深拷貝。 正例 #2:只需要重寫物件的`clone`方法即可。 ~~~java @Override public Object clone() throws CloneNotSupportedException { Resume clone = (Resume) super.clone(); Date cloneDate = (Date) clone.getWorkExperience().clone(); clone.setWorkExperience(cloneDate); return clone; } ~~~ ![image-20200915185444082](https://i.loli.net/2020/09/15/KSCmsb9jXg5Yaup.png) 其實就是對**淺拷貝**的欄位再進行**深拷貝**。 以上面用到的`Date`引用型別物件為例: ![image-20200915185501800](https://i.loli.net/2020/09/15/5UKJAdzrw1hFoC6.png) 可以看到`Date`是實現了`Cloneable`介面的,即表示`Date`也是可以進行`clone`(克隆)的。只需要將淺拷貝的`Date`再使用`clone`方法進行一次深拷貝,再賦值給`clone`的物件即可。具體參照上面重寫的`clone`方法。 總結: > 這種模式是實現了一個原型介面,該介面用於建立當前物件的克隆。當直接建立物件的代價比較大時,則採用這種模式。例如,一個物件需要在一個高代價的資料庫操作之後被建立。我們可以快取該物件,在下一個請求時返回它的克隆,在需要的時候更新資料庫,以此來減少資料庫呼叫。 ## 建造者模式 簡述:建造者模式(Builder Pattern)使用多個簡單的物件一步一步構建成一個複雜的物件。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。 先明確實體類 ~~~java public class Computer { private String cpu; private String gpu; private String Hd; private String RAM; public String getCpu() { return cpu; } public void setCpu(String cpu) { this.cpu = cpu; } public String getGpu() { return gpu; } public void setGpu(String gpu) { this.gpu = gpu; } public String getHd() { return Hd; } public void setHd(String hd) { Hd = hd; } public String getRAM() { return RAM; } public void setRAM(String RAM) { this.RAM = RAM; } @Override public String toString() { return "Computer{" + "cpu='" + cpu + '\'' + ", gpu='" + gpu + '\'' + ", Hd='" + Hd + '\'' + ", RAM='" + RAM + '\'' + '}'; } } ~~~ #### 反例 #1 : 廢話不多說,建立物件。 ~~~java public class negtive_01 { public static void main(String[] args) { Computer computer_01 = new Computer(); computer_01.setCpu("9700k"); computer_01.setGpu("gtx2080ti"); computer_01.setHd("SSD--1T"); computer_01.setRAM("32G"); Computer computer_02 = new Computer(); computer_02.setCpu("9600k"); computer_02.setGpu("gtx1080ti"); computer_02.setHd("SSD--500G"); computer_02.setRAM("16G"); System.out.println(computer_02); System.out.println(computer_01); } } ~~~ ==缺陷==:建造複雜物件的時候,客戶端程式猿要炸,造成客戶端程式碼臃腫,且違反[迪米特法則](https://www.cnblogs.com/l1ng14/p/13662445.html#迪米特法則(最少知道原則))。 #### 反例 #2: ~~~java public class negtive_02 { /*=============服務端==========================*/ static class HighComputerBuilder { private Computer computer = new Computer(); public Computer build() { computer.setCpu("9700k"); computer.setGpu("gtx2080ti"); computer.setHd("SSD--1T"); computer.setRAM("32G"); return computer; } } static class High_02ComputerBuilder { private Computer computer = new Computer(); public Computer build() { computer.setCpu("9600k"); computer.setGpu("gtx1080ti"); computer.setHd("SSD--500G"); computer.setRAM("16G"); return computer; } } /*=====================客戶端===============================*/ public static void main(String[] args) { HighComputerBuilder builder_01 = new HighComputerBuilder(); Computer computer_01 =builder_01.build(); High_02ComputerBuilder builder_02 = new High_02ComputerBuilder(); Computer computer_02 =builder_02.build(); System.out.println(computer_01); System.out.println(computer_02); } } ~~~ 創造了建造者類,用於建立複雜物件。 `UML`類圖如下: ![image-20200916133832600](https://i.loli.net/2020/09/16/ShJAEsbVH5TwMU2.png) ==缺陷==:建造者遺漏部分建造步驟編譯也會通過,會造成建造出來的物件不符合要求。比如,漏執行某一步驟時,使得部分值為`null`,後續物件屬性被呼叫時,可能會丟擲空指標`NullPointerException`異常,會造成程式崩潰。 #### 反例 #3: ~~~java public class negtive_03 { /*=============服務端==========================*/ interface ComputerBuilder{ Computer build(); void setCpu(); void setGpu(); void setHd(); void setRAM(); } static class HighComputerBuilder implements ComputerBuilder{ private Computer computer = new Computer(); @Override public Computer build() { return computer; } @Override public void setCpu() { computer.setCpu("9700k"); } @Override public void setGpu() { computer.setGpu("gtx2080ti"); } @Override public void setHd() { computer.setHd("SSD--1T"); } @Override public void setRAM() { computer.setRAM("32G"); } } static class High_02ComputerBuilder implements ComputerBuilder{ private Computer computer = new Computer(); @Override public Computer build() { return computer; } @Override public void setCpu() { computer.setCpu("9600k"); } @Override public void setGpu() { computer.setGpu("gtx1080ti"); } @Override public void setHd() { computer.setHd("SSD--500G"); } @Override public void setRAM() { computer.setRAM("16G"); } } /*==============客戶端=====================================*/ public static void main(String[] args) { HighComputerBuilder builder_01 = new HighComputerBuilder(); builder_01.setCpu(); builder_01.setGpu(); builder_01.setHd(); builder_01.setRAM(); Computer computer_01 =builder_01.build(); High_02ComputerBuilder builder_02 = new High_02ComputerBuilder(); builder_02.setCpu(); builder_02.setGpu(); builder_02.setHd(); builder_02.setRAM(); Computer computer_02 =builder_02.build(); System.out.println(computer_01); System.out.println(computer_02); } } ~~~ 創造了建造者介面,**建立者不會再遺漏步驟。** `UML`類圖如下: ![image-20200916161454045](https://i.loli.net/2020/09/16/6LM1cvJusEVrykT.png) ==缺陷==: - 每一個`builder`都要自己去呼叫`setXXX`方法進行建立,造成程式碼重複。 - 需要客戶端自己執行建立步驟,建造複雜物件的時候,容易造成客戶端程式碼臃腫,且違反[迪米特法則](https://www.cnblogs.com/l1ng14/p/13662445.html#迪米特法則(最少知道原則))。而且**客戶端會出現遺漏步驟的情況**。又回到了原點的感覺??? #### 正例 #1: ~~~java public class postive { /*=============服務端==========================*/ interface ComputerBuilder{ Computer getComputer(); void setCpu(); void setGpu(); void setHd(); void setRAM(); } static class HighComputerBuilder implements ComputerBuilder { private Computer computer = new Computer(); @Override public Computer getComputer() { return computer; } @Override public void setCpu() { computer.setCpu("9700k"); } @Override public void setGpu() { computer.setGpu("gtx2080ti"); } @Override public void setHd() { computer.setHd("SSD--1T"); } @Override public void setRAM() { computer.setRAM("32G"); } } static class High_02ComputerBuilder implements ComputerBuilder { private Computer computer = new Computer(); @Override public Computer getComputer() { return computer; } @Override public void setCpu() { computer.setCpu("9600k"); } @Override public void setGpu() { computer.setGpu("gtx1080ti"); } @Override public void setHd() { computer.setHd("SSD--500G"); } @Override public void setRAM() { computer.setRAM("16G"); } } //指揮者 static class Director { public Computer build(ComputerBuilder builder){ builder.setCpu(); builder.setGpu(); builder.setRAM(); builder.setHd(); return builder.getComputer(); } } /*==============客戶端=====================================*/ public static void main(String[] args) { Director director = new Director(); Computer computer_01 =director.build(new HighComputerBuilder()); Computer computer_02 =director.build(new High_02ComputerBuilder()); System.out.println(computer_01); System.out.println(computer_02); } } ~~~ `UML`類圖如下: ![image-20200916162337824](https://i.loli.net/2020/09/16/cmwryWLiNR7vYEK.png) 此時在需要增加一個不同配置的`A_Computer`型別計算機,只需要編寫一個`A_Builder`類實現`ComputerBuilder`介面,再傳給指揮者`Director`進行建立即可得到一個`A_Computer`型別的`Computer`物件。符合[開閉原則](https://www.cnblogs.com/l1ng14/p/13662445.html#開閉原則)。 總結一下建造者模式的==優點==: - 建立物件的過程保持穩定。(通過`ComputerBuilder`介面保持穩定) - 建立過程只需要編寫一次(通過實現`ComputerBuilder`介面的類保持穩定) - 保持擴充套件性,需要建立新型別的物件時,只需要建立新的`Builder`,再使用指揮者`Director`進行呼叫進行建立即可。 - 增加指揮者`Director`保證步驟穩定執行,客戶端不需要知道建立物件的具體步驟,符合[迪米特法則](https://www.cnblogs.com/l1ng14/p/13662445.html#迪米特法則(最少知道原則))。 --- ### 建造者模式和工廠模式的區別 - 工廠模式注重`new`一個物件就可以,是否得到了這一物件,更多地關注`new`的結果。 - 建造者模式注重保證`new`的物件穩定可用,保證不出現細節缺漏,更多關注`new`的細節、過程。