個(gè)人學(xué)習(xí)筆記分享萝招,當(dāng)前能力有限,請(qǐng)勿貶低存捺,菜鳥互學(xué)槐沼,大佬繞道
如有勘誤,歡迎指出和討論捌治,本文后期也會(huì)進(jìn)行修正和補(bǔ)充
前言
原型模式是一種創(chuàng)建型設(shè)計(jì)模式母赵, 使你能夠復(fù)制已有對(duì)象, 而又無需使代碼依賴它們所屬的類具滴。
原型(Prototype)模式的定義如下:用一個(gè)已經(jīng)創(chuàng)建的實(shí)例作為原型凹嘲,通過復(fù)制該原型對(duì)象來創(chuàng)建一個(gè)和原型相同或相似的新對(duì)象。在這里构韵,原型實(shí)例指定了要?jiǎng)?chuàng)建的對(duì)象的種類周蹭。用這種方式創(chuàng)建對(duì)象非常高效,根本無須知道對(duì)象創(chuàng)建的細(xì)節(jié)疲恢。
當(dāng)直接創(chuàng)建對(duì)象的代價(jià)比較大時(shí)凶朗,則采用這種模式。
1.介紹
使用目的:已知原型實(shí)例的情況下显拳,可以獲得相同的實(shí)例對(duì)象
使用時(shí)機(jī):需要?jiǎng)討B(tài)的生成和刪除實(shí)例模型
解決問題:動(dòng)態(tài)的創(chuàng)建和刪除實(shí)例
實(shí)現(xiàn)方法:實(shí)現(xiàn)Cloneable
類的clone()
方法
使用場(chǎng)景:
- 通過new一個(gè)對(duì)象需要極其繁瑣的數(shù)據(jù)準(zhǔn)備或者權(quán)限棚愤,那么推薦使用原型模式
- 對(duì)象初始化需要消耗大量資源的時(shí)候,從舊的對(duì)象進(jìn)行克隆出新對(duì)象杂数,即可不必重復(fù)初始化
- 一個(gè)對(duì)象可能有多個(gè)修改者宛畦,那么可以克隆出去多份新對(duì)象供其使用
應(yīng)用實(shí)例:
- 細(xì)胞分裂
- JAVA 中的
Object clone()
方法
優(yōu)點(diǎn):
- 性能提高,構(gòu)建新的對(duì)象只需要拷貝舊的對(duì)象即可
- 逃避構(gòu)造函數(shù)的約束揍移,根本不經(jīng)過構(gòu)造函數(shù)
缺點(diǎn):
- 配備克隆方法需要對(duì)類的功能進(jìn)行通盤考慮次和,這對(duì)于全新的類不是很難,但對(duì)于已有的類不一定很容易那伐,特別當(dāng)一個(gè)類引用不支持串行化的間接對(duì)象踏施,或者引用含有循環(huán)結(jié)構(gòu)的時(shí)候
- 類必須實(shí)現(xiàn)
Cloneable
接口。
注意事項(xiàng):既然是拷貝罕邀,那么必須有源對(duì)象才能實(shí)現(xiàn)畅形,否則還是得構(gòu)建一個(gè)全新的對(duì)象
分類:通過拷貝的方法和其內(nèi)容,分為淺拷貝和深拷貝
-
淺拷貝:只拷貝源對(duì)象的基本數(shù)據(jù)诉探,而不拷貝容器日熬,引用等等,一般實(shí)現(xiàn)
Cloneable
類并重寫clone()
方法 -
深拷貝:拷貝源對(duì)象的一切阵具,包括數(shù)據(jù)、容器、引用等等豺旬,一般通過實(shí)現(xiàn)
Serializable
讀取二進(jìn)制流,直接復(fù)制出新對(duì)象揣炕,也可通過其他方式實(shí)現(xiàn)
2.實(shí)現(xiàn)方案
深拷貝或者淺拷貝是結(jié)果,而非簡(jiǎn)單的由方案決定东跪,比如在
clone
方法中拷貝全部?jī)?nèi)容畸陡,也可以達(dá)到深拷貝的效果
請(qǐng)注意,示例中修正新對(duì)象僅為了測(cè)試虽填,實(shí)際應(yīng)用中請(qǐng)視情況處理丁恭,理論上應(yīng)當(dāng)保持新舊對(duì)象盡可能一致
2.1.方案1:實(shí)現(xiàn)Cloneable類
實(shí)現(xiàn)
Cloneable
類并重寫clone()
方法即可,在該方法中設(shè)定好需要拷貝的內(nèi)容斋日,調(diào)用源對(duì)象中的該方法即可獲得新的對(duì)象
步驟
-
定義一個(gè)實(shí)現(xiàn)了
Cloneable
接口的抽象類牲览,并實(shí)現(xiàn)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 { //克隆對(duì)象 clone = (Animal) super.clone(); //克隆對(duì)象里的復(fù)雜對(duì)象,若不拷貝則會(huì)使用源對(duì)象里的復(fù)雜對(duì)象 //clone.typeSet = (List<String>) ((ArrayList) this.typeSet).clone(); //修正新對(duì)象 clone.type = type + "Cloned"; clone.typeSet.add(clone.type); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } }
這里我們?cè)诳寺r(shí)恶守,會(huì)對(duì)新對(duì)象進(jìn)行修正第献,而其余內(nèi)容保持與源對(duì)象一致
復(fù)雜對(duì)象后面進(jìn)行測(cè)試
-
定義實(shí)體類,實(shí)現(xiàn)抽象類兔港,簡(jiǎn)單易懂
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("喵!"); } }
-
定義數(shù)據(jù)源初始化和調(diào)用方法
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()
方法中初始化出兩個(gè)對(duì)象衫樊,并將其存儲(chǔ)入map在
getAnimal()
方法中取出對(duì)象的克隆 -
測(cè)試調(diào)用
public static void main(String[] args) { //初始化 initAnimal(); //第一輪測(cè)試 System.out.println("test turn 1:"); Animal dog1 = getAnimal("dog"); Animal cat1 = getAnimal("cat"); dog1.say(); cat1.say(); //第二輪測(cè)試 System.out.println("test turn 2:"); Animal dog2 = dog1.clone(); dog2.say(); //第三輪測(cè)試 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 {
//克隆對(duì)象
clone = (Animal) super.clone();
//克隆對(duì)象里的復(fù)雜對(duì)象飒赃,若不拷貝則會(huì)使用源對(duì)象里的復(fù)雜對(duì)象
//clone.typeSet = (List<String>) ((ArrayList) this.typeSet).clone();
//修正新對(duì)象
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();
//第一輪測(cè)試
System.out.println("test turn 1:");
Animal dog1 = getAnimal("dog");
Animal cat1 = getAnimal("cat");
dog1.say();
cat1.say();
//第二輪測(cè)試
System.out.println("test turn 2:");
Animal dog2 = dog1.clone();
dog2.say();
//第三輪測(cè)試
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;
}
}
}
運(yùn)行結(jié)果
分析
- 第一輪測(cè)試為一次克隆的結(jié)果载佳,
type
后追加Cloned
,typeSet
包括2個(gè)數(shù)據(jù)兑徘,分別是舊type和新type - 第二輪測(cè)試為二次克隆的結(jié)果刚盈,
type
后追加兩個(gè)Cloned
,typeSet
包括3個(gè)數(shù)據(jù) - 第三輪仍為一次克隆的結(jié)果挂脑,
type
后只追加了一個(gè)Cloned
,但typeSet
卻有3個(gè)數(shù)據(jù)欲侮,與二次克隆是同一個(gè)typeSet
也就是說克隆后崭闲,使用的typeSet
是同一個(gè)
==這也就是所說的淺拷貝:只拷貝數(shù)據(jù),而容器威蕉、引用等則直接使用源對(duì)象的刁俭,并不進(jìn)行拷貝==
==如果需要深拷貝,則需要在clone()
方法中對(duì)復(fù)雜對(duì)象進(jìn)行復(fù)制==
如果啟用clone()
方法中注釋掉的代碼韧涨,運(yùn)行結(jié)果會(huì)變成下面這樣
可以看到現(xiàn)在的typeSet
不是同一個(gè)了牍戚,只是拷貝的時(shí)候復(fù)制了里面的內(nèi)容
2.2.方案2:序列化后進(jìn)行復(fù)制
即將對(duì)象以二進(jìn)制完全復(fù)制一份新的侮繁,再轉(zhuǎn)換為對(duì)象,通常使用
Serializable
和數(shù)據(jù)流一起實(shí)現(xiàn)
步驟
這里僅修改
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 {
//將對(duì)象寫到流里
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();
//修正新對(duì)象
clone.type = type + "Cloned";
clone.typeSet.add(clone.type);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return clone;
}
}
將對(duì)象寫入流里宪哩,再讀出來,并強(qiáng)制轉(zhuǎn)換為對(duì)應(yīng)類的對(duì)象即可
運(yùn)行結(jié)果
分析
可以看到結(jié)果是深拷貝第晰,因?yàn)樾聦?duì)象時(shí)由數(shù)據(jù)流轉(zhuǎn)換來的锁孟,復(fù)雜對(duì)象保留的是數(shù)據(jù),而非引用地址茁瘦,那么自然會(huì)構(gòu)造新的
3.后記
實(shí)例中我們預(yù)先初始化所有源對(duì)象品抽,使用map
存儲(chǔ),使用的時(shí)候取出對(duì)應(yīng)對(duì)象的克隆體
其實(shí)這就是最常見的應(yīng)用場(chǎng)景甜熔,我們可以快速獲得對(duì)應(yīng)的對(duì)象圆恤,不需要每次都初始化和進(jìn)行構(gòu)造,而對(duì)深拷貝對(duì)象的修改都不會(huì)影響源對(duì)象腔稀,也就可以保證每次的源對(duì)象是相同且純凈的
一次初始化盆昙,之后便可快速得到新的對(duì)象,我們也可以在初始化的時(shí)候進(jìn)行數(shù)據(jù)預(yù)設(shè)等等烧颖,視業(yè)務(wù)情況處理
總之弱左,還是很實(shí)用的對(duì)吧~
作者:Echo_Ye
WX:Echo_YeZ
EMAIL :echo_yezi@qq.com
個(gè)人站點(diǎn):在搭了在搭了。炕淮。拆火。(右鍵 - 新建文件夾)