設計模式 #3 (原型模式、建造者模式)
文章中所有工程程式碼和UML
建模檔案都在我的這個GitHub
的公開庫--->DesignPattern。Star
來一個好嗎?秋梨膏!
原型模式
簡述:用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。
反例 #1 :
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:
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:
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));
}
}
這是一個關於深拷貝、淺拷貝的問題。
深拷貝與淺拷貝
淺拷貝(淺複製):clone
得到的物件a
其實只是對被clone
物件b
的引用,即物件a
是指向b
物件上的。
深拷貝(深複製):clone
得到的物件a
是對被clone
物件b
的複製,即物件a
和b
是兩個不同的物件,a
只複製了b
的內容。
Java
中,對一物件的clone
中深拷貝物件的基本型別欄位,淺拷貝引用型別欄位。
這時候要將淺拷貝改為深拷貝。
正例 #2:只需要重寫物件的clone
方法即可。
@Override
public Object clone() throws CloneNotSupportedException {
Resume clone = (Resume) super.clone();
Date cloneDate = (Date) clone.getWorkExperience().clone();
clone.setWorkExperience(cloneDate);
return clone;
}
其實就是對淺拷貝的欄位再進行深拷貝。
以上面用到的Date
引用型別物件為例:
可以看到Date
是實現了Cloneable
介面的,即表示Date
也是可以進行clone
(克隆)的。只需要將淺拷貝的Date
再使用clone
方法進行一次深拷貝,再賦值給clone
的物件即可。具體參照上面重寫的clone
方法。
總結:
這種模式是實現了一個原型介面,該介面用於建立當前物件的克隆。當直接建立物件的代價比較大時,則採用這種模式。例如,一個物件需要在一個高代價的資料庫操作之後被建立。我們可以快取該物件,在下一個請求時返回它的克隆,在需要的時候更新資料庫,以此來減少資料庫呼叫。
建造者模式
簡述:建造者模式(Builder Pattern)使用多個簡單的物件一步一步構建成一個複雜的物件。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。
先明確實體類
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 :
廢話不多說,建立物件。
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);
}
}
缺陷:建造複雜物件的時候,客戶端程式猿要炸,造成客戶端程式碼臃腫,且違反迪米特法則。
反例 #2:
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
類圖如下:
缺陷:建造者遺漏部分建造步驟編譯也會通過,會造成建造出來的物件不符合要求。比如,漏執行某一步驟時,使得部分值為null
,後續物件屬性被呼叫時,可能會丟擲空指標NullPointerException
異常,會造成程式崩潰。
反例 #3:
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
類圖如下:
缺陷:
-
每一個
builder
都要自己去呼叫setXXX
方法進行建立,造成程式碼重複。 -
需要客戶端自己執行建立步驟,建造複雜物件的時候,容易造成客戶端程式碼臃腫,且違反迪米特法則。而且客戶端會出現遺漏步驟的情況。又回到了原點的感覺???
正例 #1:
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
類圖如下:
此時在需要增加一個不同配置的A_Computer
型別計算機,只需要編寫一個A_Builder
類實現ComputerBuilder
介面,再傳給指揮者Director
進行建立即可得到一個A_Computer
型別的Computer
物件。符合開閉原則。
總結一下建造者模式的優點:
-
建立物件的過程保持穩定。(通過
ComputerBuilder
介面保持穩定) -
建立過程只需要編寫一次(通過實現
ComputerBuilder
介面的類保持穩定) -
保持擴充套件性,需要建立新型別的物件時,只需要建立新的
Builder
,再使用指揮者Director
進行呼叫進行建立即可。 -
增加指揮者
Director
保證步驟穩定執行,客戶端不需要知道建立物件的具體步驟,符合迪米特法則。
建造者模式和工廠模式的區別
- 工廠模式注重
new
一個物件就可以,是否得到了這一物件,更多地關注new
的結果。 - 建造者模式注重保證
new
的物件穩定可用,保證不出現細節缺漏,更多關注new
的細節、過程。