一、概念 用原型實例指定創建對象的種類,並通過拷貝這些原型創建新的對象。 二、模式動機 當已有一個對像,暫且稱之為原型對象,需要一個新的對像,該對像和已有的原型對像具有相同的類型,且裡面的屬性大部分相同,或者只有個別不同時,這時就可以用原型模式,克隆原型對像,產生一個新的對像,並對新的對像屬性進行適 ...
一、概念
用原型實例指定創建對象的種類,並通過拷貝這些原型創建新的對象。
二、模式動機
當已有一個對像,暫且稱之為原型對象,需要一個新的對像,該對像和已有的原型對像具有相同的類型,且裡面的屬性大部分相同,或者只有個別不同時,這時就可以用原型模式,克隆原型對像,產生一個新的對像,並對新的對像屬性進行適當的修改,已適應系統需求。比如新生產的同一批次同一型號的筆記本電腦,如果每個電腦都是一個實例對像,這些電腦配置都是相同的,唯一不同可能是序列號不同,如何實例化所有的電腦,這時就可以用一個原型電腦,去克隆出所有的電腦,只需對克隆出來的新電腦設置正確的序列號就可以了。
三、模式的結構
角色分析:
1.IPrototype:聲明一個克隆自身的介面
2.ConcretePrototype(ConcretePrototypeA,ConcretePrototypeA): 實現了IPrototype介面, 這些類真正實現了克隆自身的功能
3.Client:讓一個原型對象克隆自身產生一個新的對象。
樣例代碼:
package prototype; /** * 聲明一個克隆自身的介面,所有實現該介面的類實例,都可以克隆自身產生一個新的對象 * @ClassName: IPrototype * @author beteman6988 * @date 2017年10月23日 下午9:47:35 * */ public interface IPrototype { /** * 實現克隆自身的介面方法 * @Title: clone * @param @return * @return IPrototype * @throws */ public IPrototype clone(); }
package prototype; /** * 實現了克隆介面的具體實現對象 * @ClassName: ConcretePrototype * @author beteman6988 * @date 2017年10月23日 下午9:51:13 * */ public class ConcretePrototypeA implements IPrototype { /** * 實現克隆功能的具本方法 */ @Override public IPrototype clone() { //最簡單的方式就是new 一個新對像,並一一將自已的屬性值複製到新的對像裡面 IPrototype cloneObj=new ConcretePrototypeA(); //將本對像的屬性值賦於新的對像 //如 cloneObj.setProperty(this.propety); 等 return cloneObj; } }
package prototype; /** * 實現了克隆介面的具體實現對象 * @ClassName: ConcretePrototype * @author beteman6988 * @date 2017年10月23日 下午9:51:13 * */ public class ConcretePrototypeB implements IPrototype { /** * 實現克隆功能的具本方法 */ @Override public IPrototype clone() { //最簡單的方式就是new 一個新對像,並一一將自已的屬性值複製到新的對像裡面 IPrototype cloneObj=new ConcretePrototypeB(); //將本對像的屬性值賦於新的對像 //如 cloneObj.setProperty(this.propety); 等 return cloneObj; } }
package prototype; /** * 客戶端程式,從已有實例克隆出一個新的對象 * @ClassName: Client * @author beteman6988 * @date 2017年10月23日 下午10:06:18 * */ public class Client { private IPrototype instance=new ConcretePrototypeA(); private void operation() { //從已有實例 instance 克隆出一個新的對象 cloneObj IPrototype cloneObj=instance.clone(); } }
關於淺度克隆、深度克隆及java對該原型模式的支持
1.淺度克隆與深度克隆:在講原型模式是,淺度克隆與深度克隆是逃不開的話題,上面的示例都是淺度克隆,那麼什麼是淺度克隆和深度克隆呢?
淺度克隆:只負責按值傳遞的數據,如基本數據類型和String ,如果是引用,則原型對像和克隆出的對像指的是同一個引用地址。
深度克隆:除了淺度克隆要克隆的值外,引用對像也會被克隆,克隆出的對像和原型對像中的引用是不同的,指上不同的地址空間。
2. java對淺度克隆的支持:java.lang.Object.clone()方法,該方法為protected native方法,表明繼承於他的類都可以調用該方法,又由於java中的所有類都繼承於java.lang.Object,所以說java中的所有類都可以以super.clone()的方式調用java.lang.Object.clone()方法。當子類通過調用java.lang.Object.clone()方法實現克隆時,子類必須實現Cloneable標識介面,該標識介面的作用就是在運行時通知java虛擬機可以安全的在這個類上使用clone()方法,通過該方法得到一個對像的克隆,如下圖所示:
代碼如下:
package prototype.cloneable; /** * 聲明一個克隆自身的介面,所有實現該介面的類實例,都可以克隆自身產生一個新的對象 * @ClassName: IPrototype * @author beteman6988 * @date 2017年10月23日 下午9:47:35 * */ public interface IPrototype extends Cloneable { /** * 實現克隆自身的介面方法 * @Title: clone * @param @return * @return IPrototype * @throws */ public IPrototype clone(); }
package prototype.cloneable; /** * 實現了克隆介面的具體實現對象 * @ClassName: ConcretePrototype * @author beteman6988 * @date 2017年10月23日 下午9:51:13 * */ public class ConcretePrototypeA implements IPrototype { /** * 實現克隆功能的具本方法 */ @Override public IPrototype clone() { try { return (IPrototype)super.clone(); } catch ( Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } }
package prototype.cloneable; /** * 客戶端程式 跟據已有實例,克隆出一個新的實例 * @ClassName: Client * @author beteman6988 * @date 2017年10月23日 下午11:37:18 * */ public class Client { private IPrototype instance=new ConcretePrototypeA(); public void option() { IPrototype p=instance.clone(); } }
3.java對深度克隆的支持:在java裡面利用串列化Serilization可以實現深度克隆,他要求原型類及原型類裡面的引用類都需要實現Serializable標識介面,他的實現思想是通過將原型對像寫到流裡面,然後再從流里讀出來重建對像。還是基於上面的例子,如下圖:
package prototype; import java.io.Serializable; public class A implements Serializable { }
package prototype; import java.io.Serializable; /** * 聲明一個克隆自身的介面,所有實現該介面的類實例,都可以克隆自身產生一個新的對象 * @ClassName: IPrototype * @author beteman6988 * @date 2017年10月23日 下午9:47:35 * */ public interface IPrototype extends Serializable { /** * 實現克隆自身的介面方法 * @Title: clone * @param @return * @return IPrototype * @throws */ public IPrototype clone(); }
package prototype; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** * 實現了克隆介面的具體實現對象 * @ClassName: ConcretePrototype * @author beteman6988 * @date 2017年10月23日 下午9:51:13 * */ public class ConcretePrototypeA implements IPrototype { private A a =new A(); /** * 實現克隆功能的具本方法 */ @Override public IPrototype clone() { try { //將對像寫入流中 ByteArrayOutputStream bo=new ByteArrayOutputStream(); ObjectOutputStream oo=new ObjectOutputStream(bo); oo.writeObject(this); //將對像從流中讀出 ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi=new ObjectInputStream(bi); return (IPrototype)oi.readObject(); } catch ( Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } } }
package prototype; /** * 客戶端程式 * @ClassName: Client * @author beteman6988 * @date 2017年10月23日 下午11:37:18 * */ public class Client { public static void main(String[] args) { IPrototype prototype=new ConcretePrototypeA(); IPrototype copy= prototype.clone(); } }
通過調試見下圖,可以看出copy對象及內部的a 引用對象與原型對象是不同的對象,如下圖示:
4.java通過逐級淺克隆實現深度克隆:通過java.lang.Object.clone()對java對像及內部的引用逐級克隆也是可以實現的,難點只是無法確定克隆的層次,因為引用裡面還有可能有引用,引用的層次無法確定。
四、模式樣例
現實生活中複印機和細胞分裂就是很貼切的例子,當我們將一份文件放到複印機中,複印機可以將原件自身複印(克隆)出另一份文件,兩份文件內容相同,但是是不同的兩個實體。如下圖所示:
package prototype; /** * 具有複印功能的介面 * @ClassName: CopyAble * @author beteman6988 * @date 2017年10月23日 下午11:19:45 * */ public interface CopyAble { public CopyAble copy(); }
/** * 可以複製的一張紙 * @ClassName: Paper * @author beteman6988 * @date 2017年10月23日 下午11:28:57 * */ public class Paper implements CopyAble { private String header; //文件頭 private String content; //文件內容 private String footer; //文件尾 /** * 複製出內容一樣的一張紙 */ @Override public CopyAble copy() { Paper anotherPaper=new Paper(); anotherPaper.setHeader(this.header); anotherPaper.setContent(this.content); anotherPaper.setFooter(this.footer); return anotherPaper; } public String getHeader() { return header; } public void setHeader(String header) { this.header = header; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getFooter() { return footer; } public void setFooter(String footer) { this.footer = footer; } }
/** * 可以複製的一張紙 * @ClassName: Paper * @author beteman6988 * @date 2017年10月23日 下午11:28:57 * */ public class Paper implements CopyAble { private String header; //文件頭 private String content; //文件內容 private String footer; //文件尾 /** * 複製出內容一樣的一張紙 */ @Override public CopyAble copy() { Paper anotherPaper=new Paper(); anotherPaper.setHeader(this.header); anotherPaper.setContent(this.content); anotherPaper.setFooter(this.footer); return anotherPaper; } public String getHeader() { return header; } public void setHeader(String header) { this.header = header; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getFooter() { return footer; } public void setFooter(String footer) { this.footer = footer; } }
/** * 可複製的一張照片 * @ClassName: Picture * @author beteman6988 * @date 2017年10月23日 下午11:30:51 * */ public class Picture implements CopyAble { private String data; //照片內容 /** * 複製出內容一模一樣的一張照片 */ @Override public CopyAble copy() { // TODO Auto-generated method stub return null; } public String getData() { return data; } public void setData(String data) { this.data = data; } }
package prototype; /** * 複印機類,可以複印一切可以複印的東西(即實現CopyAble的實現類) * @ClassName: CopyMachine * @author beteman6988 * @date 2017年10月23日 下午11:34:35 * */ public class CopyMachine { /** * 對傳入的可複製對像進行複製 * @Title: runCopy * @param @param source * @param @return * @return CopyAble * @throws */ public CopyAble runCopy(CopyAble source) { return source.copy(); } }
package prototype; /** * 客戶端程式 使用複印機,對現有的可複製資料進行複製 * @ClassName: Client * @author beteman6988 * @date 2017年10月23日 下午11:37:18 * */ public class Client { public static void main(String[] args) { CopyMachine machine=new CopyMachine(); Paper aPaper=new Paper(); aPaper.setHeader("文件頭內空"); aPaper.setContent("文件內容"); aPaper.setFooter("文件尾內容"); Paper anotherPaper= (Paper) machine.runCopy(aPaper); //複印機複印出另一文件 System.out.println(anotherPaper.getHeader()); System.out.println(anotherPaper.getContent()); System.out.println(anotherPaper.getFooter());
System.out.println(aPaper==anotherPaper);
System.out.println(aPaper.getClass()==anotherPaper.getClass());
}
}
運行結果如下:
文件頭內空
文件內容
文件尾內容
false
true
五、模式的約束
對於第三方客戶提供的實現類,這種類往往無權進行修改,這時如何實現對該類實例的克隆,是相當麻煩,這時可以藉助一些通用性比較好的工具類來完成,如apache 的org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)
對於克隆出的新對象和原對象之間需滿足以下條件約束
1 對於任何對象 x.clone()!=x 克隆對像和原對象不是同一個對像 ,該條件必須滿足
2. x.clone().getClass()=x.getClass() ,克隆對像和原對像的類型必須相同
3.x.clone().equals(x) , 可選
六、模式的變體與擴展
在實際的開發中,對於需要單獨實現克隆的情況相對較少,經常使用的是一些寫好的通用工具類,如apache 的org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig) ,其實現原理是通過java的反射機制來實現的,拿來就可以直接使用。
七、與其它模式的關係
如抽像工廠模式或工廠方法模式,這些模式關註點是要產生什麼樣的對像,對於如何產生這樣的對像,適當的情況下就可以結合原型模式,如通過已有的對像產生想要的對像。
八、模式優缺點
優點:對客戶隱藏具體的實現類型,原型模式的客戶端只知道原型介面的類型,並不知道具體的實現類型,從而減少了客戶端對這些具體實現類的依賴。
缺點:在實現深度克隆時比較麻煩,對於對像裡面有引用,引用裡面可能還有引用,每個引用層級的類都必須正確實現clone()方法才能讓所有層級的對像正確的實現克隆。