1. 簡(jiǎn)介
原型模式是一種簡(jiǎn)單、易使用的創(chuàng)建型設(shè)計(jì)模式,通過(guò)給出一個(gè)原型對(duì)象來(lái)指明所創(chuàng)建的對(duì)象的類型,然后用復(fù)制這個(gè)原型對(duì)象的辦法創(chuàng)建出更多同類型的對(duì)象。原型模式要求對(duì)象實(shí)現(xiàn)一個(gè)可以“克隆”自身的接口,這樣就可以通過(guò)復(fù)制一個(gè)實(shí)例對(duì)象本身來(lái)創(chuàng)建一個(gè)新的實(shí)例。這樣一來(lái),通過(guò)原型實(shí)例創(chuàng)建新的對(duì)象,就不再需要關(guān)心這個(gè)實(shí)例本身的類型,只要實(shí)現(xiàn)了克隆自身的方法,就可以通過(guò)這個(gè)方法來(lái)獲取新的對(duì)象,而無(wú)須再去通過(guò)new來(lái)創(chuàng)建。
2.UML

(1)客戶角色:客戶類提出創(chuàng)建對(duì)象的請(qǐng)求。
(2)抽象原型角色:這是一個(gè)抽象角色,通常由一個(gè)java接口或抽象類實(shí)現(xiàn),具體原型都需要實(shí)現(xiàn)該接口。
(3)具體原型角色:客戶端所需要的被復(fù)制的對(duì)象。
我們都知道Object是祖宗,所有的Java類都繼承至Object,而Object類提供了一個(gè)clone()方法,該方法可以將一個(gè)java對(duì)象復(fù)制一份,因此在java中可以直接使用clone()方法來(lái)復(fù)制一個(gè)對(duì)象。但是需要實(shí)現(xiàn)clone的Java類必須要實(shí)現(xiàn)一個(gè)接口:Cloneable.該接口表示該類能夠復(fù)制且具體復(fù)制的能力,如果不實(shí)現(xiàn)該接口而直接調(diào)用clone()方法會(huì)拋出CloneNotSupportedException異常。
Java中任何實(shí)現(xiàn)了Cloneable接口的類都可以通過(guò)調(diào)用clone()方法來(lái)復(fù)制一份自身然后傳給調(diào)用者。一般而言,clone()方法滿足:
(1) 對(duì)任何的對(duì)象x,都有x.clone() !=x,即克隆對(duì)象與原對(duì)象不是同一個(gè)對(duì)象。
(2) 對(duì)任何的對(duì)象x,都有x.clone().getClass()==x.getClass(),即克隆對(duì)象與原對(duì)象的類型一樣。
(3) 如果對(duì)象x的equals()方法定義恰當(dāng),那么x.clone().equals(x)應(yīng)該成立。
3. 示例
抽象原型角色
public interface Prototype{
public Prototype clone();
public String getName();
public void setName(String name);
}
具體原型角色
public class ConcretePrototype1 implements Prototype {
private String name;
public Prototype clone(){
ConcretePrototype1 prototype = new ConcretePrototype1();
prototype.setName(this.name);
return prototype;
}
public String toString(){
return "Now in Prototype1 , name = " + this.name;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
public class ConcretePrototype2 implements Prototype {
private String name;
public Prototype clone(){
ConcretePrototype2 prototype = new ConcretePrototype2();
prototype.setName(this.name);
return prototype;
}
public String toString(){
return "Now in Prototype2 , name = " + this.name;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
原型管理器角色保持一個(gè)聚集,作為對(duì)所有原型對(duì)象的登記,這個(gè)角色提供必要的方法,供外界增加新的原型對(duì)象和取得已經(jīng)登記過(guò)的原型對(duì)象。
public class PrototypeManager {
/**
* 用來(lái)記錄原型的編號(hào)和原型實(shí)例的對(duì)應(yīng)關(guān)系
*/
private static Map<String,Prototype> map = new HashMap<String,Prototype>();
/**
* 私有化構(gòu)造方法,避免外部創(chuàng)建實(shí)例
*/
private PrototypeManager(){}
/**
* 向原型管理器里面添加或是修改某個(gè)原型注冊(cè)
* @param prototypeId 原型編號(hào)
* @param prototype 原型實(shí)例
*/
public synchronized static void setPrototype(String prototypeId , Prototype prototype){
map.put(prototypeId, prototype);
}
/**
* 從原型管理器里面刪除某個(gè)原型注冊(cè)
* @param prototypeId 原型編號(hào)
*/
public synchronized static void removePrototype(String prototypeId){
map.remove(prototypeId);
}
/**
* 獲取某個(gè)原型編號(hào)對(duì)應(yīng)的原型實(shí)例
* @param prototypeId 原型編號(hào)
* @return 原型編號(hào)對(duì)應(yīng)的原型實(shí)例
* @throws Exception 如果原型編號(hào)對(duì)應(yīng)的實(shí)例不存在,則拋出異常
*/
public synchronized static Prototype getPrototype(String prototypeId) throws Exception{
Prototype prototype = map.get(prototypeId);
if(prototype == null){
throw new Exception("您希望獲取的原型還沒(méi)有注冊(cè)或已被銷毀");
}
return prototype;
}
}
客戶端角色
public class Client {
public static void main(String[]args){
try{
Prototype p1 = new ConcretePrototype1();
PrototypeManager.setPrototype("p1", p1);
//獲取原型來(lái)創(chuàng)建對(duì)象
Prototype p3 = PrototypeManager.getPrototype("p1").clone();
p3.setName("張三");
System.out.println("第一個(gè)實(shí)例:" + p3);
//有人動(dòng)態(tài)的切換了實(shí)現(xiàn)
Prototype p2 = new ConcretePrototype2();
PrototypeManager.setPrototype("p1", p2);
//重新獲取原型來(lái)創(chuàng)建對(duì)象
Prototype p4 = PrototypeManager.getPrototype("p1").clone();
p4.setName("李四");
System.out.println("第二個(gè)實(shí)例:" + p4);
//有人注銷了這個(gè)原型
PrototypeManager.removePrototype("p1");
//再次獲取原型來(lái)創(chuàng)建對(duì)象
Prototype p5 = PrototypeManager.getPrototype("p1").clone();
p5.setName("王五");
System.out.println("第三個(gè)實(shí)例:" + p5);
}catch(Exception e){
e.printStackTrace();
}
}
}
4.深拷貝與淺拷貝
無(wú)論你是自己實(shí)現(xiàn)克隆方法,還是采用Java提供的克隆方法,都存在一個(gè)淺度克隆和深度克隆的問(wèn)題。
淺度克隆
只負(fù)責(zé)克隆按值傳遞的數(shù)據(jù)(比如基本數(shù)據(jù)類型、String類型),而不復(fù)制它所引用的對(duì)象,換言之,所有的對(duì)其他對(duì)象的引用都仍然指向原來(lái)的對(duì)象。
深度克隆
除了淺度克隆要克隆的值外,還負(fù)責(zé)克隆引用類型的數(shù)據(jù)。那些引用其他對(duì)象的變量將指向被復(fù)制過(guò)的新對(duì)象,而不再是原有的那些被引用的對(duì)象。換言之,深度克隆把要復(fù)制的對(duì)象所引用的對(duì)象都復(fù)制了一遍,而這種對(duì)被引用到的對(duì)象的復(fù)制叫做間接復(fù)制。深度克隆要深入到多少層,是一個(gè)不易確定的問(wèn)題。在決定以深度克隆的方式復(fù)制一個(gè)對(duì)象的時(shí)候,必須決定對(duì)間接復(fù)制的對(duì)象時(shí)采取淺度克隆還是繼續(xù)采用深度克隆。因此,在采取深度克隆時(shí),需要決定多深才算深。此外,在深度克隆的過(guò)程中,很可能會(huì)出現(xiàn)循環(huán)引用的問(wèn)題,必須小心處理。

另外,可能會(huì)有疑惑,這樣一種原型模式相比賦值構(gòu)造函數(shù)有什么優(yōu)勢(shì)呢?
Base b = new Derived();
現(xiàn)在要克隆b怎么辦?
只有Derived(b),但是如果只有抽象基類的調(diào)用接口(Base) 則你根本不知道具體要實(shí)例化哪個(gè)類(Derived),而且你也不需要知道,所以根本無(wú)法克隆.這時(shí)原形模式就顯現(xiàn)優(yōu)勢(shì)了 即只要調(diào)用clone 函數(shù)即可.
5. 優(yōu)缺點(diǎn)
原型模式的優(yōu)點(diǎn)
原型模式允許在運(yùn)行時(shí)動(dòng)態(tài)改變具體的實(shí)現(xiàn)類型。原型模式可以在運(yùn)行期間,由客戶來(lái)注冊(cè)符合原型接口的實(shí)現(xiàn)類型,也可以動(dòng)態(tài)地改變具體的實(shí)現(xiàn)類型,看起來(lái)接口沒(méi)有任何變化,但其實(shí)運(yùn)行的已經(jīng)是另外一個(gè)類實(shí)例了。因?yàn)榭寺∫粋€(gè)原型就類似于實(shí)例化一個(gè)類。
原型模式的缺點(diǎn)
原型模式最主要的缺點(diǎn)是每一個(gè)類都必須配備一個(gè)克隆方法。配備克隆方法需要對(duì)類的功能進(jìn)行通盤考慮,這對(duì)于全新的類來(lái)說(shuō)不是很難,而對(duì)于已經(jīng)有的類不一定很容易,特別是當(dāng)一個(gè)類引用不支持序列化的間接對(duì)象,或者引用含有循環(huán)結(jié)構(gòu)的時(shí)候。