原型模式屬於對象的創建模式.通過給出一個原型對象來指定鎖創建對象的類型,然後用複製這個原型對象的辦法創建更多同類型的對象,這就是選型模式的用意.
1. 原型模式的結構
原型模式對象實現一個可以”克隆”隻身的接口,這樣就可以通過複製一個實例對象本身來創建一個新的實例.這樣一來,通過原型實例創建新的對象,就不在需要關心這個實例本身的類型,只要實現了克隆隻身的方法,就可以通過這個方法來獲取新的對象,而無須再去通過new來創建
原型模式又兩種表現形式:(1) 簡單形式 (2) 登記形式,這兩種表現僅僅是原型模式的不同實現
2. 簡單形式的原型模式
這種形式涉及到三種角色:
(1) 客戶(Client)端角色:客戶端提出創建對象的請求
(2) 抽象原型(Prototype)角色: 這是一個抽象角色,通常由一個Java接口或Java抽象類去實現.此類角色給出所有的具體原型類所需的接口
(3) 具體原型(Concrete Prototype)角色: 被複制的對象.此角色需要實現抽象的原型角色所需要的接口
源代碼
抽象原型角色
public interface Prototype{
//克隆自身方法
public Prototype clone();
}
具體原型1
public ConcretePrototype1 implements Prototype{
public Prototype clone(){
Prototype prototype = new ConcretePrototype1();
return prototype;
}
}
具體原型2
public ConcretePrototype2 implements Prototype{
public Prototype clone(){
Prototype prototype = new ConcretePrototype2();
return prototype;
}
}
客戶端角色
public class client{
//持有需要使用原型接口對象
private Prototype prototype;
//構造方法,傳入搜需要的對象
public client(Prototype prototype){
this.prototype = prototype;
}
public void operation(Prototype example){
//需要創建原型接口的對象
Prototype copyPrototype = prototype.clone();
}
}
3. 登記形式的原型模式
相對於簡單的原型模式,它多了一個原型管理器(PrototypeManager)角色,改角色作用是:創建具體原型類的對象,並記錄每一個 被創建的對象
抽象原型
public interface Prototype{
public Prototype clone();
public String getName();
public void setName(String name);
}
具體原型
public class ConcretePrototype1 implements Prototye{
private String name;
public Prototype clone(){
ConcretePrototype1 prototype = new ConcretePrototype1();
prototyep.setName(this.name);
return prototype;
}
public String toString(){
return "Now in Prototype1 , name = " + this.name;
}
public String getName(){
return name;
}
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;
}
}
原型管理器角色保持一個聚集,作爲對所有原型對象的登記,這個角色提供必要的方法,共外界增加新的原型對象和取得已經登記過的原型對象.
public class PrototypeManager{
//用來記錄原型的編號和原型實例的對應關係
private static Map<String, Prototype> map = new HashMap<String, Prototype>();
//私有構造
private PrototypeManager(){}
//向原型管理器裏面添加或是修改某個原型註冊
public synchronized static void setPrototype(String prototypeId, Prototype prototype){
map.put(prototypeId, prototype);
}
public synchronized static removePrototype(String prototypeId){
map.remove(prototypeId);
}
public synchronized static void getPrototype(String prototypeId) throws Exception{
Prototype prototype = map.get(prototypeId);
if(prototype == null){
throw new Exception("您希望獲取的原型還沒有註冊或已被銷燬");
}
return prototype;
}
}
客戶端角色
public class Client {
public static void main(String[]args){
try{
Prototype p1 = new ConcretePrototype1();
PrototypeManager.setPrototype("p1", p1);
//獲取原型來創建對象
Prototype p3 = PrototypeManager.getPrototype("p1").clone();
p3.setName("張三");
System.out.println("第一個實例:" + p3);
//有人動態的切換了實現
Prototype p2 = new ConcretePrototype2();
PrototypeManager.setPrototype("p1", p2);
//重新獲取原型來創建對象
Prototype p4 = PrototypeManager.getPrototype("p1").clone();
p4.setName("李四");
System.out.println("第二個實例:" + p4);
//有人註銷了這個原型
PrototypeManager.removePrototype("p1");
//再次獲取原型來創建對象
Prototype p5 = PrototypeManager.getPrototype("p1").clone();
p5.setName("王五");
System.out.println("第三個實例:" + p5);
}catch(Exception e){
e.printStackTrace();
}
}
}
兩種形式的比較
其兩種形式和登記形式的原型模式各有其長短處和短處
如果需要創建的原型對象數目而且比較固定的話,可以採取第一種形式.在這種情況下,原型對象的引用可以由客戶端自己保存.
如果要創建的原型對象數目不固定的話,可以採取第二種形式。在這種情況下,客戶端不保存對原型對象的引用,這個任務被交給管理員對象。在複製一個原型對象之前,客戶端可以查看管理員對象是否已經有一個滿足要求的原型對象。如果有,可以直接從管理員類取得這個對象引用;如果沒有,客戶端就需要自行復制此原型對象。
Java的克隆方法
Java的所有課都是從java.lang.Object類繼承而來, 而Object類提供protected Object clone()方法對對象進行復制,子類當然也可以把這個方法置換掉,提供滿足自己需要的複製方法.對象的複製有一個基本問題,就是對象通常都有對其他的對象的引用.當使用Object類的clone() 方法複製一個對象時,此對象對其他對象的引用也同時會被複制一份.
Java語言提供的Cloneable接口只起一個作用,就是在運行時期通知Java虛擬機可以安全的在這個類使用clone()方法.通過調用這個clone()方法可以得到一個對象的複製.由於Object類本身並不實現Cloneable接口,因此如果所有考慮的類沒有實現Cloneable接口時,調用clone()方法會拋出CloneNotSupportedException異常.
4. 克隆滿足的條件
clone()方法將對象複製一份並反還給調用者.所謂複製的含義與clone()方法是怎麼實現的.
以下描述:
(1)對任何的對象x,都有:x.clone()! = x. 換言之,克隆對象與原則對象不是同一個對象.
(2)對任何的對象x,都有:x.clone().getClass() == x.getClass(),換言之,克隆對象與原對象的類型一樣。
(3)如果對象x的equals()方法定義其恰當的話,那麼x.clone().equals(x)應當成立的。
淺克隆和深克隆
淺克隆: 只負責克隆按值傳遞的數據(比如基本類型數據, String類型),而不是複製它所引用的對象,換言之,所有的對其它.
深克隆: 除了淺度克隆要克隆的值外,還負責克隆引用類型的數據。那些引用其他對象的變量將指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深度克隆把要複製的對象所引用的對象都複製了一遍,而這種對被引用到的對象的複製叫做間接複製。
深度克隆要深入到多少層,是一個不易確定的問題。在決定以深度克隆的方式複製一個對象的時候,必須決定對間接複製的對象時採取淺度克隆還是繼續採用深度克隆。因此,在採取深度克隆時,需要決定多深纔算深。此外,在深度克隆的過程中,很可能會出現循環引用的問題,必須小心處理。
5. 利用序列化實現深度克隆
把對象寫到流裏的過程是序列化的過程;而把對象從流中讀出來的過程則反序列化過程.應當指出的是,寫到流裏的是對對象的一個拷貝,而原對象仍然存在於JVM裏面。
在Java語言裏深度克隆一個對象,常常可以先使對象實現Serializable接口,然後把對象(實際上只是對象的拷貝)寫到一個流裏(序列化),再從流裏讀回來(反序列化),便可以重建對象。
public Object deepClone() throws IOException,ClassNotFountException{
//將對象寫到流裏
ByteArrayOutStream bos = new ByteArrayOutStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//從流裏讀出來
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
}
這樣做的前提就是對象以及對象內部所有引用到的對象都是可序列化的,否則,就需要仔細考察那些不可序列化的對象可否設成transient,從而將之排除在複製過程之外。
淺度克隆顯然比深度克隆更容易實現,因爲Java語言的所有類都會繼承一個clone()方法,而這個clone()方法所做的正式淺度克隆。
有一些對象,比如線程(Thread)對象或Socket對象,是不能簡單複製或共享的。不管是使用淺度克隆還是深度克隆,只要涉及這樣的間接對象,就必須把間接對象設成transient而不予複製;或者由程序自行創建出相當的同種對象,權且當做複製件使用。
原型模式的有點:
原型模式允許在運行時動態改變具體的實現類型.原型模式可以在運行期間,由客戶來註冊符合原型接口的實現類型,也可以動態的改變具體實現類型,看起來接口沒有任何變化,但其實運行的已經是另外一個類實例了.因爲克隆一個原型就類似於實例化一個類
原型模式缺點:
原型模式最主要的缺點是每一個類必須匹配一個克隆方法.匹備克隆方法需要對類的功能進行銅盤考慮,這對於全新的類來說不是很難,但對於已經有的類不一定很容易,特別是當一個類引用不支持粗劣化的間接對象,或者引用含有循環結構的時候.