設計模式 #3 (原型模式、建造者模式)
阿新 • • 發佈:2020-09-16
# 設計模式 #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`的細節、過程。