原型模式
所謂的原型模式敢朱,無非就是從一個對象再創(chuàng)建另一個可定制的對象,而且不需要知道任何創(chuàng)建的細節(jié)栖茉。所謂的原型模式,其實質(zhì)就是編程需要中的克隆技術(shù)孵延,以某個對象為原型吕漂,復(fù)制出新的對象。只是需要注意深復(fù)制與淺復(fù)制的問題尘应。
原型模式(Prototype Pattern)是用于創(chuàng)建重復(fù)的對象惶凝,同時又能保證性能吼虎。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式苍鲜。
這種模式是實現(xiàn)了一個原型接口思灰,該接口用于創(chuàng)建當前對象的克隆。當直接創(chuàng)建對象的代價比較大時混滔,則采用這種模式洒疚。例如,一個對象需要在一個高代價的數(shù)據(jù)庫操作之后被創(chuàng)建坯屿。我們可以緩存該對象油湖,在下一個請求時返回它的克隆,在需要的時候更新數(shù)據(jù)庫领跛,以此來減少數(shù)據(jù)庫調(diào)用乏德。
介紹
意圖:用原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象吠昭。
主要解決:在運行期建立和刪除原型喊括。
何時使用: 1、當一個系統(tǒng)應(yīng)該獨立于它的產(chǎn)品創(chuàng)建矢棚,構(gòu)成和表示時瘾晃。 2、當要實例化的類是在運行時刻指定時幻妓,例如蹦误,通過動態(tài)裝載。 3肉津、為了避免創(chuàng)建一個與產(chǎn)品類層次平行的工廠類層次時强胰。 4、當一個類的實例只能有幾個不同狀態(tài)組合中的一種時妹沙。建立相應(yīng)數(shù)目的原型并克隆它們可能比每次用合適的狀態(tài)手工實例化該類更方便一些偶洋。
如何解決:利用已有的一個原型對象,快速地生成和原型對象一樣的實例距糖。
關(guān)鍵代碼: 1玄窝、實現(xiàn)克隆操作,在 JAVA 繼承 Cloneable悍引,重寫 clone()恩脂,在 .NET 中可以使用 Object 類的 MemberwiseClone() 方法來實現(xiàn)對象的淺拷貝或通過序列化的方式來實現(xiàn)深拷貝。 2趣斤、原型模式同樣用于隔離類對象的使用者和具體類型(易變類)之間的耦合關(guān)系俩块,它同樣要求這些"易變類"擁有穩(wěn)定的接口。
應(yīng)用實例: 1、細胞分裂玉凯。 2势腮、JAVA 中的 Object clone() 方法。
優(yōu)點: 1漫仆、性能提高捎拯。 2、逃避構(gòu)造函數(shù)的約束盲厌。
缺點: 1署照、配備克隆方法需要對類的功能進行通盤考慮,這對于全新的類不是很難狸眼,但對于已有的類不一定很容易藤树,特別當一個類引用不支持串行化的間接對象浴滴,或者引用含有循環(huán)結(jié)構(gòu)的時候拓萌。 2、必須實現(xiàn) Cloneable 接口升略。
使用場景: 1微王、資源優(yōu)化場景。 2品嚣、類初始化需要消化非常多的資源炕倘,這個資源包括數(shù)據(jù)、硬件資源等翰撑。 3罩旋、性能和安全要求的場景。 4眶诈、通過 new 產(chǎn)生一個對象需要非常繁瑣的數(shù)據(jù)準備或訪問權(quán)限涨醋,則可以使用原型模式。 5逝撬、一個對象多個修改者的場景浴骂。 6、一個對象需要提供給其他對象訪問宪潮,而且各個調(diào)用者可能都需要修改其值時溯警,可以考慮使用原型模式拷貝多個對象供調(diào)用者使用。 7狡相、在實際項目中梯轻,原型模式很少單獨出現(xiàn),一般是和工廠方法模式一起出現(xiàn)尽棕,通過 clone 的方法創(chuàng)建一個對象檩淋,然后由工廠方法提供給調(diào)用者。原型模式已經(jīng)與 Java 融為渾然一體,大家可以隨手拿來使用蟀悦。
注意事項:與通過對一個類進行實例化來構(gòu)造新對象不同的是媚朦,原型模式是通過拷貝一個現(xiàn)有對象生成新對象的。淺拷貝實現(xiàn) Cloneable日戈,重寫询张,深拷貝是通過實現(xiàn) Serializable 讀取二進制流。
應(yīng)用場景
你一定遇到過大篇幅getter浙炼、setter賦值的場景份氧。例如這樣的代碼:
public class ContentDataDto {
/**
* 內(nèi)容主鍵
*/
private Integer contentId;
/**
* Item寬度(以單元格為單位)
*/
private Integer itemWidth;
/**
* Item高度(以單元格為單位)
*/
private Integer itemHeight;
/**
* Item的(x軸)橫坐標(以單元格為單位)
*/
private Integer itemx;
/**
* Item的(y軸)縱坐標(以單元格為單位)
*/
private Integer itemy;
/**
* 布局ID
*/
private Integer layoutId;
/**
* 業(yè)務(wù)數(shù)據(jù)id
*/
private String bsid;
/**
* 標題(可包含簡介等)
*/
private String title;
/**
* 內(nèi)容名稱
*/
private String tag;
/**
* 順序
*/
private Integer prior;
/**
* 數(shù)據(jù)來源:0-后臺手動添加;1-同步請求添加
*/
private Integer dataSource;
/**
* 內(nèi)容評分
*/
private String score;
...
}
代碼非常工整弯屈,命名非常規(guī)范蜗帜,注釋也寫的很全面,大家覺得這樣的代碼優(yōu)雅嗎资厉?我認為厅缺,這樣的代碼屬于純體力勞動。那么原型模式宴偿,能幫助我們解決這樣的問題湘捎。原型模式(Prototype Pattern)是指原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象窄刘。
原型模式主要適用于以下場景:
1窥妇、類初始化消耗資源較多。
2娩践、new產(chǎn)生的一個對象需要非常繁瑣的過程(數(shù)據(jù)準備活翩、訪問權(quán)限等)
3、構(gòu)造函數(shù)比較復(fù)雜翻伺。
4材泄、循環(huán)體中生產(chǎn)大量對象時。
在Spring中穆趴,原型模式應(yīng)用得非常廣泛脸爱。例如scope=“prototype”,在我們經(jīng)常用的JSON.parseObject()也是一種原型模式未妹。下面簿废,我們來看看原型模式類結(jié)構(gòu)圖:
簡單克隆
一個標準的原型模式代碼,應(yīng)該是這樣設(shè)計的络它。先創(chuàng)建原型Prototype接口:
public interface Prototype{
Prototype clone();
}
創(chuàng)建具體需要克隆的對象ConcretePrototype :
public class ConcretePrototypeA implements Prototype {
private int age;
private String name;
private List hobbies;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List getHobbies() {
return hobbies;
}
public void setHobbies(List hobbies) {
this.hobbies = hobbies;
}
@Override
public ConcretePrototypeA clone() {
ConcretePrototypeA concretePrototype = new ConcretePrototypeA();
concretePrototype.setAge(this.age);
concretePrototype.setName(this.name);
concretePrototype.setHobbies(this.hobbies);
return concretePrototype;
}
}
創(chuàng)建Client對象:
public class Client {
private Prototype prototype;
public Client(Prototype prototype){
this.prototype = prototype;
}
public Prototype startClone(Prototype concretePrototype){
return (Prototype)concretePrototype.clone();
}
}
測試代碼:
public class PrototypeTest {
public static void main(String[] args) {
// 創(chuàng)建一個具體的需要克隆的對象
ConcretePrototypeA concretePrototype = new ConcretePrototypeA();
// 填充屬性族檬,方便測試
concretePrototype.setAge(18);
concretePrototype.setName("prototype");
List hobbies = new ArrayList<String>();
concretePrototype.setHobbies(hobbies);
System.out.println(concretePrototype);
// 創(chuàng)建Client對象,準備開始克隆
Client client = new Client(concretePrototype);
ConcretePrototypeA concretePrototypeClone = (ConcretePrototypeA) client.startClone(concretePrototype);
System.out.println(concretePrototypeClone);
System.out.println("克隆對象中的引用類型地址值:" + concretePrototypeClone.getHobbies());
System.out.println("原對象中的引用類型地址值:" + concretePrototype.getHobbies());
System.out.println("對象地址比較:"+(concretePrototypeClone.getHobbies() == concretePrototype.getHobbies()));
}
}
運行結(jié)果:
ConcretePrototypeA@1b6d3586
ConcretePrototypeA@4554617c
克隆對象中的引用類型地址值:[]
原對象中的引用類型地址值:[]
對象地址比較:true
Process finished with exit code 0
從測試結(jié)果看出hobbies的引用地址是相同的化戳,意味著復(fù)制的不是值单料,而是引用的地址埋凯。這樣的話,如果我們修改任意一個對象中的屬性值扫尖,concretePrototype 和concretePrototypeCone的hobbies值都會改變白对。這就是我們常說的淺克隆。只是完整復(fù)制了值類型數(shù)據(jù)换怖,沒有賦值引用對象甩恼。換言之,所有的引用對象仍然指向原來的對象沉颂,顯然不是我們想要的結(jié)果条摸。下面我們來看深度克隆繼續(xù)改造。
深度克隆
我們換一個場景铸屉,大家都知道齊天大圣钉蒲。首先它是一只猴子,有七十二般變化彻坛,把一根毫毛就可以吹出千萬個潑猴顷啼,手里還拿著金箍棒,金箍棒可以變大變小小压。這就是我們耳熟能詳?shù)脑湍J降慕?jīng)典體現(xiàn)线梗。
創(chuàng)建原型猴子Monkey類:
public class Monkey {
public int height;
public int weight;
public Date birthday;
}
創(chuàng)建引用對象金箍棒Jingubang類:
public class JinGuBang implements Serializable {
public float h = 100;
public float d = 10;
public void big(){
this.d *= 2;
this.h *= 2;
}
public void small(){
this.d /= 2;
this.h /= 2;
}
}
創(chuàng)建具體的對象齊天大圣QiTianDaSheng類:
public class QiTianDaSheng extends Monkey implements Cloneable,Serializable {
public JinGuBang jinGuBang;
public QiTianDaSheng(){
//只是初始化
this.birthday = new Date();
this.jinGuBang = new JinGuBang();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return this.deepClone();
}
public Object deepClone(){
try{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
QiTianDaSheng copy = (QiTianDaSheng)ois.readObject();
copy.birthday = new Date();
return copy;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
public QiTianDaSheng shallowClone(QiTianDaSheng target){
QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
qiTianDaSheng.height = target.height;
qiTianDaSheng.weight = target.height;
qiTianDaSheng.jinGuBang = target.jinGuBang;
qiTianDaSheng.birthday = new Date();
return qiTianDaSheng;
}
}
測試代碼:
public class DeepCloneTest {
public static void main(String[] args) {
QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
try {
QiTianDaSheng clone = (QiTianDaSheng)qiTianDaSheng.clone();
System.out.println("深克乱凇:" + (qiTianDaSheng.jinGuBang == clone.jinGuBang));
} catch (Exception e) {
e.printStackTrace();
}
QiTianDaSheng q = new QiTianDaSheng();
QiTianDaSheng n = q.shallowClone(q);
System.out.println("淺克碌∫妗:" + (q.jinGuBang == n.jinGuBang));
}
}
運行結(jié)果:
深克隆:false
淺克埋觥:true
Process finished with exit code 0
克隆破壞單例模式
如果我們克隆的目標的對象是單例對象蜻牢,那意味著,深克隆就會破壞單例偏陪。實際上防止克隆破壞單例解決思路非常簡單抢呆,禁止深克隆便可。要么你我們的單例類不實現(xiàn)Cloneable接口笛谦;要么我們重寫clone()方法抱虐,在clone方法中返回單例對象即可,具體代碼如下:
@Override
protected Object clone() throws CloneNotSupportedException {
return this.deepClone();
}
Cloneable源碼分析
先看我們常用的ArrayList就實現(xiàn)了Cloneable接口饥脑,來看代碼clone()方法的實現(xiàn):
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
一些信息
路漫漫其修遠兮,吾將上下而求索
碼云:https://gitee.com/javacoo
QQ群:164863067
作者/微信:javacoo
郵箱:xihuady@126.com