【設計模式(四)】原型模式
個人學習筆記分享,當前能力有限,請勿貶低,菜鳥互學,大佬繞道
如有勘誤,歡迎指出和討論,本文後期也會進行修正和補充
前言
原型模式是一種建立型設計模式, 使你能夠複製已有物件, 而又無需使程式碼依賴它們所屬的類。
原型(Prototype)模式的定義如下:用一個已經建立的例項作為原型,通過複製該原型物件來建立一個和原型相同或相似的新物件。在這裡,原型例項指定了要建立的物件的種類。用這種方式建立物件非常高效,根本無須知道物件建立的細節。
當直接建立物件的代價比較大時,則採用這種模式。
1.介紹
使用目的:已知原型例項的情況下,可以獲得相同的例項物件
使用時機:需要動態的生成和刪除例項模型
解決問題:動態的建立和刪除例項
實現方法:實現Cloneable
類的clone()
方法
使用場景:
- 通過new一個物件需要極其繁瑣的資料準備或者許可權,那麼推薦使用原型模式
- 物件初始化需要消耗大量資源的時候,從舊的物件進行克隆出新物件,即可不必重複初始化
- 一個物件可能有多個修改者,那麼可以克隆出去多份新物件供其使用
應用例項:
- 細胞分裂
- JAVA 中的
Object clone()
方法
優點:
- 效能提高,構建新的物件只需要拷貝舊的物件即可
- 逃避建構函式的約束,根本不經過建構函式
缺點:
- 配備克隆方法需要對類的功能進行通盤考慮,這對於全新的類不是很難,但對於已有的類不一定很容易,特別當一個類引用不支援序列化的間接物件,或者引用含有迴圈結構的時候
- 類必須實現
Cloneable
介面。
注意事項:既然是拷貝,那麼必須有源物件才能實現,否則還是得構建一個全新的物件
分類:通過拷貝的方法和其內容,分為淺拷貝和深拷貝
- 淺拷貝:只拷貝源物件的基本資料,而不拷貝容器,引用等等,一般實現
Cloneable
類並重寫clone()
方法 - 深拷貝:拷貝源物件的一切,包括資料、容器、引用等等,一般通過實現
Serializable
讀取二進位制流,直接複製出新物件,也可通過其他方式實現
2.實現方案
深拷貝或者淺拷貝是結果,而非簡單的由方案決定,比如在
clone
方法中拷貝全部內容,也可以達到深拷貝的效果
請注意,示例中修正新物件僅為了測試,實際應用中請視情況處理,理論上應當保持新舊物件儘可能一致
2.1.方案1:實現Cloneable類
實現
Cloneable
類並重寫clone()
方法即可,在該方法中設定好需要拷貝的內容,呼叫源物件中的該方法即可獲得新的物件
步驟
-
定義一個實現了
Cloneable
介面的抽象類,並實現clone()
方法abstract class Animal implements Cloneable { protected String type; protected List<String> typeSet = new ArrayList<>(); public Animal(String type) { this.type = type; typeSet.add(type); } void say() { System.out.println("myType is " + type); System.out.println("myTypeSet is " + String.join(",", typeSet)); } public Animal clone() { Animal clone = null; try { //克隆物件 clone = (Animal) super.clone(); //克隆物件裡的複雜物件,若不拷貝則會使用源物件裡的複雜物件 //clone.typeSet = (List<String>) ((ArrayList) this.typeSet).clone(); //修正新物件 clone.type = type + "Cloned"; clone.typeSet.add(clone.type); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } }
這裡我們在克隆時,會對新物件進行修正,而其餘內容保持與源物件一致
複雜物件後面進行測試
-
定義實體類,實現抽象類,簡單易懂
class Dog extends Animal { public Dog() { super("dog"); } @Override void say() { super.say(); System.out.println("汪!"); } } class Cat extends Animal { public Cat() { super("cat"); } @Override void say() { super.say(); System.out.println("喵!"); } }
-
定義資料來源初始化和呼叫方法
private Map<String, Animal> animalMap = new HashMap<>(); public void initAnimal() { Dog dog = new Dog(); animalMap.put("dog", dog); Cat cat = new Cat(); animalMap.put("cat", cat); } public Animal getAnimal(String type) { if (animalMap.containsKey(type)) { return animalMap.get(type).clone(); } else { return null; } }
在
initAnimal()
方法中初始化出兩個物件,並將其儲存入map在
getAnimal()
方法中取出物件的克隆 -
測試呼叫
public static void main(String[] args) { //初始化 initAnimal(); //第一輪測試 System.out.println("test turn 1:"); Animal dog1 = getAnimal("dog"); Animal cat1 = getAnimal("cat"); dog1.say(); cat1.say(); //第二輪測試 System.out.println("test turn 2:"); Animal dog2 = dog1.clone(); dog2.say(); //第三輪測試 System.out.println("test turn 3:"); dog1.say(); System.out.println("clone equals:" + Objects.equals(dog1.typeSet, dog2.typeSet)); }
完整程式碼
package com.company.clone;
import java.util.*;
class Animal implements Cloneable {
protected String type;
protected List<String> typeSet = new ArrayList<>();
public Animal(String type) {
this.type = type;
typeSet.add(type);
}
void say() {
System.out.println("myType is " + type);
System.out.println("myTypeSet is " + String.join(",", typeSet));
}
public Animal clone() {
Animal clone = null;
try {
//克隆物件
clone = (Animal) super.clone();
//克隆物件裡的複雜物件,若不拷貝則會使用源物件裡的複雜物件
//clone.typeSet = (List<String>) ((ArrayList) this.typeSet).clone();
//修正新物件
clone.type = type + "Cloned";
clone.typeSet.add(clone.type);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
class Dog extends Animal {
public Dog() {
super("dog");
}
@Override
void say() {
super.say();
System.out.println("汪!");
}
}
class Cat extends Animal {
public Cat() {
super("cat");
}
@Override
void say() {
super.say();
System.out.println("喵!");
}
}
public class CloneTest {
public static void main(String[] args) {
//初始化
initAnimal();
//第一輪測試
System.out.println("test turn 1:");
Animal dog1 = getAnimal("dog");
Animal cat1 = getAnimal("cat");
dog1.say();
cat1.say();
//第二輪測試
System.out.println("test turn 2:");
Animal dog2 = dog1.clone();
dog2.say();
//第三輪測試
System.out.println("test turn 3:");
dog1.say();
System.out.println("clone equals:" + Objects.equals(dog1.typeSet, dog2.typeSet));
}
private static Map<String, Animal> animalMap = new HashMap<>();
public static void initAnimal() {
Dog dog = new Dog();
animalMap.put("dog", dog);
Cat cat = new Cat();
animalMap.put("cat", cat);
}
public static Animal getAnimal(String type) {
if (animalMap.containsKey(type)) {
return animalMap.get(type).clone();
} else {
return null;
}
}
}
執行結果
分析
- 第一輪測試為一次克隆的結果,
type
後追加Cloned
,typeSet
包括2個數據,分別是舊type和新type - 第二輪測試為二次克隆的結果,
type
後追加兩個Cloned
,typeSet
包括3個數據 - 第三輪仍為一次克隆的結果,
type
後只追加了一個Cloned
,但typeSet
卻有3個數據,與二次克隆是同一個typeSet
也就是說克隆後,使用的typeSet
是同一個
這也就是所說的淺拷貝:只拷貝資料,而容器、引用等則直接使用源物件的,並不進行拷貝
如果需要深拷貝,則需要在clone()
方法中對複雜物件進行復制
如果啟用clone()
方法中註釋掉的程式碼,執行結果會變成下面這樣
可以看到現在的typeSet
不是同一個了,只是拷貝的時候複製了裡面的內容
2.2.方案2:序列化後進行復制
即將物件以二進位制完全複製一份新的,再轉換為物件,通常使用
Serializable
和資料流一起實現
步驟
這裡僅修改
clone()
方法,其餘步驟與方案1一致
class Animal implements Serializable {
protected String type;
protected List<String> typeSet = new ArrayList<>();
public Animal(String type) {
this.type = type;
typeSet.add(type);
}
void say() {
System.out.println("myType is " + type);
System.out.println("myTypeSet is " + String.join(",", typeSet));
}
public Animal clone() {
Animal clone = null;
try {
//將物件寫到流裡
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//從流裡讀回來
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
clone = (Animal) ois.readObject();
//修正新物件
clone.type = type + "Cloned";
clone.typeSet.add(clone.type);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return clone;
}
}
將物件寫入流裡,再讀出來,並強制轉換為對應類的物件即可
執行結果
分析
可以看到結果是深拷貝,因為新物件時由資料流轉換來的,複雜物件保留的是資料,而非引用地址,那麼自然會構造新的
3.後記
例項中我們預先初始化所有源物件,使用map
儲存,使用的時候取出對應物件的克隆體
其實這就是最常見的應用場景,我們可以快速獲得對應的物件,不需要每次都初始化和進行構造,而對深拷貝物件的修改都不會影響源物件,也就可以保證每次的源物件是相同且純淨的
一次初始化,之後便可快速得到新的物件,我們也可以在初始化的時候進行資料預設等等,視業務情況處理
總之,還是很實用的對吧~
作者:Echo_Ye
WX:Echo_YeZ
EMAIL :[email protected]
個人站點:在搭了在搭了。。。(右鍵 - 新建資料夾)