模式簡介
原型模式(Prototype Pattern)是用于建立重複的對象,同時又能保證性能。這種類型的設計模式屬于建立型模式,它提供了一種建立對象的最佳方式。
在軟體系統中,有時候需要多次建立某一類型的對象,為了簡化建立過程,可以隻建立一個對象,然後再通過克隆的方式複制出多個相同的對象,這就是原型模式的設計思想。
原型模式的基本工作原理是通過将一個原型對象傳給那個要發動建立的對象,這個要發動建立的對象通過請求原型對象複制原型自己來實作建立過程。
舉例說明 ????
《西遊記》中孫悟空拔毛變小猴的故事幾乎人人皆知,孫悟空可以用猴毛根據自己的形象,複制出很多跟自己長得一模一樣的身外身來。
孫悟空這種複制出多個身外身的方式在面向對象設計領域裡稱為原型(Prototype)模式。
在面向對象系統中,使用原型模式來複制一個對象自身,進而克隆出多個與原型對象一模一樣的對象
。
在軟體系統中,有些對象的建立過程較為複雜,而且有時候需要頻繁建立。原型模式通過給出一個原型對象來指明所要建立的對象的類型,然後用複制這個原型對象的辦法建立出更多同類型的對象,這就是原型模式的意圖所在。
文章目錄
- 模式簡介
- 模式結構
- 模式案例
- 案例1:原型模式執行個體之郵件複制(淺克隆)
- 案例2:原型模式執行個體之郵件複制(深克隆)
- 模式總結
- 拓展内容
- 帶原型管理器的原型模式
模式結構
原型模式包含如下角色 ????
- Prototype(抽象原型類)
抽象原型類是定義具有克隆自己的方法的接口,是所有具體原型類的公共父類,可以是抽象類,也可以是接口。
- ConcretePrototype(具體原型類)
具體原型類實作具體的克隆方法,在克隆方法中傳回自己的一個克隆對象。
- Client(客戶類)
客戶類讓一個原型克隆自身,進而建立一個新的對象。在客戶類中隻需要直接執行個體化或通過工廠方法等方式建立一個對象,再通過調用該對象的克隆方法複制得到多個相同的對象。
模式案例
原型模式的克隆分為淺克隆和深克隆。
- 淺克隆:建立一個新對象,新對象的屬性和原來對象完全相同,對于非基本類型屬性,仍指向原有屬性所指向的對象的記憶體位址。
- 深克隆:建立一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象位址。
下面通過兩個分别實作淺克隆和深克隆的執行個體來進一步學習并了解原型模式。
案例1:原型模式執行個體之郵件複制(淺克隆)
- 抽象原型類Object(無需建立)
Object作為抽象原型類,在Java語言中,所有的類都是Object的子類,在Object 中提供了克隆方法clone(),用于建立一個原型對象,其 clone()方法具體實作由JVM完成,使用者在使用時無須關心。
- 附件類 Attachment
為了更好地說明淺克隆和深克隆的差別,在本執行個體中引入了附件類Attachment,郵件類Email與附件類是組合關聯關系,在郵件類中定義一個附件類對象,作為其成員對象。
package prototype;
/**
* @author mengzhichao
* @create 2021-11-10-22:20
*/
public class Attachment {
public void download(){
System.out.println("下載下傳附件");
}
}
- 具體原型類Email(郵件類)
Email類是具體原型類,也是Object類的子類。在Java語言中,隻有實作了Cloneable接口的類才能夠使用clone()方法來進行複制,是以Email類實作了Cloneable接口。在Email類中覆寫了Object的clone()方法,通過直接或者間接調用Object的clone()方法傳回一個克隆的原型對象。在Email類中定義了一個成員對象attachment,其類型為Attachment。
package prototype;
/**
* @author mengzhichao
* @create 2021-11-10-22:18
*/
public class Email implements Cloneable {
private Attachment attachment=null;
public Email(){
this.attachment=new Attachment();
}
@Override
public Object clone(){
Email clone =null;
try {
clone=(Email) super.clone();
}catch (CloneNotSupportedException e){
System.out.println("Clone failure!");
}
return clone;
}
public Attachment getAttachment(){
return this.attachment;
}
public void display(){
System.out.println("檢視郵件");
}
}
- 用戶端測試類Client
在Client 用戶端測試類中,比較原型對象和複制對象是否一緻﹐并比較其成員對象attachment的引用是否一緻。
package prototype;
/**
* @author mengzhichao
* @create 2021-11-10-22:29
*/
public class Client {
public static void main(String[] args) {
Email email,copyEmail;
email=new Email();
copyEmail= (Email) email.clone();
System.out.println("email == copyEmail?");
System.out.println(email == copyEmail);
System.out.println("email.getAttachment() == copyEmail.getAttachment()?");
System.out.println(email.getAttachment() == copyEmail.getAttachment());
}
}
結果如下
- 通過結果可以看出,表達式(email==copyEmail)結果為false,即通過複制得到的對象與原型對象的引用不一緻,也就是說明在記憶體中存在兩個完全不同的對象,一個是原型對象,一個是克隆生成的對象。
- 但是表達式(email.getAttachment( ) == copyEmail.getAttachment())結果為true,兩個對象的成員對象是同一個,說明雖然對象本身複制了一份,但其成員對象在記憶體中沒有複制,原型對象和克隆對象維持了對相同的成員對象的引用。
案例2:原型模式執行個體之郵件複制(深克隆)
使用深克隆實作郵件複制,即複制郵件的同時複制附件。
- 附件類 Attachment
作為Email類的成員對象,在深克隆中,Attachment類型的對象也将被寫入流中,是以Attachment類也需要實作Serializable接口。
package prototype2;
import java.io.Serializable;
/**
* @author mengzhichao
* @create 2021-11-10-22:20
*/
public class Attachment implements Serializable {
public void download(){
System.out.println("下載下傳附件");
}
}
- 具體原型類Email(郵件類)
Email作為具體原型類,由于實作的是深克隆,無須使用Object的 clone()方法,是以無須實作Cloneable接口;可以通過序列化的方式實作深克隆(代碼中粗體部分),由于要将Email類型的對象寫入流中,是以Email類需要實作Serializable接口。
package prototype2;
import java.io.*;
/**
* @author mengzhichao
* @create 2021-11-10-22:18
*/
public class Email implements Serializable {
private Attachment attachment=null;
public Email(){
this.attachment=new Attachment();
}
public Object deepClone() throws IOException,ClassNotFoundException, OptionalDataException{
//将對象寫入流中
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ObjectOutputStream oos =new ObjectOutputStream(bao);
oos.writeObject(this);
//将對象從流中取出
ByteArrayInputStream bis =new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois =new ObjectInputStream(bis);
return (ois.readObject());
}
public Attachment getAttachment(){
return this.attachment;
}
public void display(){
System.out.println("檢視郵件");
}
}
- 用戶端測試類Client
在Client用戶端測試類中,我們仍然比較深克隆後原型對象和拷貝對象是否一緻,并比較其成員對象attachment的引用是否一緻。
package prototype2;
/**
* @author mengzhichao
* @create 2021-11-11-22:28
*/
public class Client {
public static void main(String[] args) {
Email email,copyEmail = null;
email=new Email();
try {
copyEmail = (Email) email.deepClone();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("email==copyEmail?");
System.out.println(email==copyEmail);
System.out.println("email.getAttachment()==copyEmail.getAttachment()?");
System.out.println(email.getAttachment()==copyEmail.getAttachment());
}
}
通過結果可以看出,表達式( email==copyEmail)結果為false,即通過複制得到的對象與原型對象的引用不一緻,表達式( email.getAttachment()==copyEmail.getAttachment())結果也為false,原型對象與克隆對象對成員對象的引用不相同,說明其成員對象也複制了一份。
模式總結
-
優缺點:原型模式最大的優點在于可以快速建立很多相同或相似的對象,簡化對象的建立過程,還可以儲存對象的一些中間狀态。
其缺點在于需要為每一個類配備一個克隆方法,是以對已有類進行改造比較麻煩﹐需要修改其源代碼,并且在實作深克隆時需要編寫較為複雜的代碼。
- 适用于:建立新對象成本較大,新的對象可以通過原型模式對已有對象進行複制來獲得。系統要儲存對象的狀态,而對象的狀态變化很小,需要避免使用分層次的工廠類來建立分層次的對象,并且類的執行個體對象隻有一個或很少的幾個組合狀态,通過複制原型對象得到新執行個體可能比使用構造函數建立一個新執行個體更加友善。
拓展内容
帶原型管理器的原型模式
- 原型模式的一種改進形式是帶原型管理器的原型模式。
原型管理器(Prototype Manager)角色建立具體原型類的對象,并記錄每一個被建立的對象。原型管理器的作用與工廠相似,其中定義了一個集合用于存儲原型對象,如果需要某個對象的一個克隆,可以通過複制集合中對應的原型對象來獲得。在原型管理器中針對抽象原型類進行程式設計,以便擴充。
下面使用代碼模拟示範一個顔色原型管理器的實作過程。
- 抽象原型類 MyColor
package prototypemanager;
/**
* @author mengzhichao
* @create 2021-11-14-10:30
*/
public interface MyColor extends Cloneable {
public Object clone();
public void display();
}
- 具體原型類 Red
package prototypemanager;
/**
* @author mengzhichao
* @create 2021-11-14-10:32
*/
public class Red implements MyColor {
@Override
public Object clone() {
Red r=null;
try {
r = (Red) super.clone();
}catch (Exception e){
}
return r;
}
@Override
public void display() {
System.out.println("This is Red");
}
}
- 具體原型類 Blue
package prototypemanager;
/**
* @author mengzhichao
* @create 2021-11-14-10:36
*/
public class Blue implements MyColor {
@Override
public Object clone() {
Blue b=null;
try {
b = (Blue) super.clone();
}catch (Exception e){
}
return b;
}
@Override
public void display() {
System.out.println("This is Blue");
}
}
- 原型管理器類 PrototypeManager
package prototypemanager;
import java.util.Hashtable;
/**
* @author mengzhichao
* @create 2021-11-14-10:37
*/
public class PrototypeManager {
private Hashtable ht =new Hashtable();
public PrototypeManager() {
ht.put("red",new Red());
ht.put("blue",new Blue());
}
public void addColor(String key,MyColor obj){
ht.put(key,obj);
}
public MyColor getColor(String key){
return (MyColor) ((MyColor)ht.get(key)).clone();
}
}
- 用戶端測試類 Client
package prototypemanager;
/**
* @author mengzhichao
* @create 2021-11-14-10:47
*/
public class Client {
public static void main(String[] args) {
PrototypeManager pm =new PrototypeManager();
MyColor obj1=pm.getColor("red");
obj1.display();
MyColor obj2=pm.getColor("red");
obj2.display();
System.out.println(obj1==obj2);
}
}
運作結果如下
- 在PrototypeManager中定義了一個 Hashtable類型的集合,使用“鍵值對”來存儲原型對象,用戶端可以通過Key來擷取對應原型對象的克隆對象。PrototypeManager類提供了工廠方法,用于傳回一個克隆對象。