面向物件設計模式之---原型模式(Prototype Pattern)
原型模式的定義是:
用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。
這個概念看上去很抽象,其實質就是我們在程式設計中經常用到的物件複製,然後我們不免又要提到一個老生常談的話題淺複製與深複製。
先看一下原型模式的UML類圖吧:
其中,Prototype是一個原型的抽象類或藉口,它裡面有一個共有方法,叫clone。ConcretePrototype1與ConcretePrototype2是兩個具體的例項,繼承或實現了Prototype。這就對應了定義中用原型例項指定建立物件的種類。Client是客戶端類,它與Prototype是關聯的關係,即在Client類的例項中,有Prototype的物件。客戶端可以通過呼叫Prototype的clone方法來對實現了Prototype的ConcretePrototype1或ConcretePrototype2的物件進行復制來建立新物件,這樣比new會有更高的執行效率。
至於它的應有場景,我就拿《大話設計模式》中的例子吧,書中舉了一個求職簡歷的例子,我們投公司一般都是一投好幾家,簡歷也要準備好幾份,有的還需要針對公司和職位量身定製部分內容,如果拿軟體來生成簡歷,就比較適合使用原型模式來設計。
現在來談談淺複製與深複製的問題,所謂淺複製,簡而言之,就是在物件中的非引用型別都會被逐位複製,引用型別複製的是引用,也就是說,在複製後的物件中,如果原有物件中含有引用型別的話,那麼複製的物件與原物件指向同一個引用型別,這裡的引用型別指的是陣列、其他物件例項等。淺複製只做到了原物件與複製物件的相對獨立;而深複製就是連引用型別也跟著複製了,原物件與複製物件完全獨立。
這個例子我們用java來實現,在Java中有一個藉口替我們實現好了Prototype,這個介面叫做cloneable。Sun官方的宣告如下
public interface Cloneable
此類實現了 Cloneable 介面,以指示 Object.clone() 方法可以合法地對該類例項進行按欄位複製。
如果在沒有實現 Cloneable 介面的例項上呼叫 Object 的 clone 方法,則會導致丟擲 CloneNotSupportedException 異常。
按照慣例,實現此介面的類應該使用公共方法重寫 Object.clone(它是受保護的)。請參閱 Object.clone(),以獲得有關重寫此方法的詳細資訊。
注意,此介面不 包含 clone 方法。因此,因為某個物件實現了此介面就克隆它是不可能的。即使 clone 方法是反射性呼叫的,也無法保證它將獲得成功。
注意紅字,也就是說,這個介面告訴計算機,這個類是可複製的。clone預設實現的是淺複製,下面就舉一個利用原型模式和cloneable介面實現深複製的例子。在簡歷中一般包含有工作經驗,我們就把它抽象出一個類。
程式碼例項如下:
import java.util.*;
//工作經驗類
class Experience implements Cloneable
{
private String time;//工作時間
private String where;//工作地點
//設定工作經驗
public void setExperience(String time , String where)
{
this.time = time;
this.where = where;
}
//顯示工作經驗
public String toString()
{
return "在"+ where + "工作過,時間為"+time+"\n";
}
//克隆方法,用於深複製
protected Object clone()throws CloneNotSupportedException
{
return super.clone();
}
}
//簡歷類
class Resume implements Cloneable
{
private String name;//名字
private int age;//年齡
private Experience exp;//工作經驗
//構造方法
public Resume()
{
this.exp = new Experience();
}
//設定名字
public void setName(String name)
{
this.name = name;
}
//設定年齡
public void setAge(int age)
{
this.age = age;
}
//設定工作經驗
public void setExp(String time , String where)
{
this.exp.setExperience(time,where);
}
//顯示簡歷資訊
public String toString()
{
return "姓名:"+this.name+" 年齡:"+ this.age+" 工作經驗:"+exp.toString();
}
//克隆方法
protected Object clone()throws CloneNotSupportedException
{
Resume obj = (Resume)super.clone();
obj.name = this.name;
obj.age = age;
obj.exp = (Experience)this.exp.clone();
return obj;
}
}
public class Main
{
public static void main(String[] args)
{
try
{
//第一份簡歷
Resume r1 = new Resume();
r1.setName("Martin");
r1.setAge(23);
r1.setExp("2016年","Tencent");
//第二份簡歷
Resume r2 = (Resume)r1.clone();
r2.setExp("2017年","Baidu");
System.out.println(r1);
System.out.println(r2);
}catch(CloneNotSupportedException e)
{
e.printStackTrace();
}
}
}
執行結果如下: