張紀中版《西遊記》以出乎意料的造型和雷人的台詞遭到廣大觀眾朋友的熱議,我們在此對該話題不作過多討論。但無論是哪個版本的《西遊記》,孫悟空都是其中的一號雄性主角,關於他(或它)拔毛變小猴的故事幾乎人人皆知,孫悟空可以用猴毛根據自己的形象,複製(又稱“複製”或“拷貝”)出很多跟自己長得一模一樣的“身外身”來。在設計模式中也存在一個類似的模式,可以通過一個原型對象複製出多個一模一樣的對象,該模式稱之為原型模式。
7.1 大同小異的工作周報
Sunny軟體公司一直使用自行開發的一套OA (Office Automatic,辦公自動化)系統進行日常工作辦理,但在使用過程中,越來越多的人對工作周報的建立和編寫模組產生了抱怨。追其原因,Sunny軟體公司的OA管理員發現,由於某些崗位每周工作存在重複性,工作周報內容都大同小異,7-1工作周報。這些周報只有一些小地方存在差異,但是現行系統每周預設建立的周報都是空白報表,使用者只能通過重新輸入或不斷複製粘貼來填寫重複的周報內容,極大降低了工作效率,浪費寶貴的時間。如何快速建立相同或者相似的工作周報,成為Sunny公司OA開發人員面臨的一個新問題。 圖7-1 工作周報 |
Sunny公司的開發人員通過對問題進行仔細分析,決定按照如下思路對工作周報模組進行重新設計和實現:
(1)除了允許使用者建立新周報外,還允許使用者將建立好的周報儲存為模板;
(2)使用者在再次建立周報時,可以建立全新的周報,還可以選擇合適的模板複製產生一份相同的周報,然後對新產生的周報根據實際情況進行修改,產生新的周報。
只要按照如上兩個步驟進行處理,工作周報的建立效率將得以大大提高。這個過程讓我們想到平時經常進行的兩個電腦基本操作:複製和粘貼,快速鍵通常為Ctrl + C和Ctrl + V,通過對已有對象的複製和粘貼,我們可以建立大量的相同對象。如何在一個物件導向系統中實現對象的複製和粘貼呢?不用著急,本章我們介紹的原型模式正為解決此類問題而誕生。
7.2 原型模式概述
在使用原型模式時,我們需要首先建立一個原型對象,再通過複製這個原型對象來建立更多同類型的對象。試想,如果連孫悟空的模樣都不知道,怎麼拔毛變小猴子呢?原型模式的定義如下:
原型模式(Prototype Pattern):使用原型執行個體指定建立對象的種類,並且通過拷貝這些原型建立新的對象。原型模式是一種對象建立型模式。 |
原型模式的工作原理很簡單:將一個原型對象傳給那個要發動建立的對象,這個要發動建立的對象通過請求原型對象拷貝自己來實現建立過程。由於在軟體系統中我們經常會遇到需要建立多個相同或者相似對象的情況,因此原型模式在真實開發中的使用頻率還是非常高的。原型模式是一種“另類”的建立型模式,建立複製對象的工廠就是原型類自身,Factory 方法由複製方法來實現。
需要注意的是通過複製方法所建立的對象是全新的對象,它們在記憶體中擁有新的地址,通常對複製所產生的對象進行修改對原型對象不會造成任何影響,每一個複製對象都是相互獨立的。通過不同的方式修改可以得到一系列相似但不完全相同的對象。
原型模式的結構7-2所示:
圖7-2 原型模式結構圖
在原型模式結構圖中包含如下幾個角色:
●Prototype(抽象原型類):它是聲明複製方法的介面,是所有具體原型類的公用父類,可以是抽象類別也可以是介面,甚至還可以是具體實作類別。
● ConcretePrototype(具體原型類):它實現在抽象原型類中聲明的複製方法,在複製方法中返回自己的一個複製對象。
● Client(客戶類):讓一個原型對象複製自身從而建立一個新的對象,在客戶類中只需要直接執行個體化或通過Factory 方法等方式建立一個原型對象,再通過調用該對象的複製方法即可得到多個相同的對象。由於客戶類針對抽象原型類Prototype編程,因此使用者可以根據需要選擇具體原型類,系統具有較好的可擴充性,增加或更換具體原型類都很方便。
原型模式的核心在於如何?複製方法,下面將介紹兩種在Java語言中常用的複製實現方法:
1.通用實現方法
通用的複製實現方法是在具體原型類的複製方法中執行個體化一個與自身類型相同的對象並將其返回,並將相關的參數傳入新建立的對象中,保證它們的成員屬性相同。示意代碼如下所示:
class ConcretePrototype implements Prototype { private String attr; //成員屬性 public void setAttr(String attr) { this.attr = attr; } public String getAttr() { return this.attr; } public Prototype clone() //複製方法 { Prototype prototype = new ConcretePrototype(); //建立新對象 prototype.setAttr(this.attr); return prototype; } } |
|
思考 能否將上述代碼中的clone()方法寫成:public Prototype clone() { return this; }?給出你的理由。 |
|
在客戶類中我們只需要建立一個ConcretePrototype對象作為原型對象,然後調用其clone()方法即可得到對應的複製對象,如下代碼所示:
Prototype obj1 = new ConcretePrototype(); obj1.setAttr("Sunny"); Prototype obj2 = obj1.clone(); |
這種方法可作為原型模式的通用實現,它與程式設計語言特性無關,任何物件導向語言都可以使用這種形式來實現對原型的複製。
2. Java語言提供的clone()方法
學過Java語言的人都知道,所有的Java類都繼承自java.lang.Object。事實上,Object類提供一個clone()方法,可以將一個Java對象複製一份。因此在Java中可以直接使用Object提供的clone()方法來實現對象的複製,Java語言中的原型模式實現很簡單。
需要注意的是能夠實現複製的Java類必須實現一個標識介面Cloneable,表示這個Java類支援被複製。如果一個類沒有實現這個介面但是調用了clone()方法,Java編譯器將拋出一個CloneNotSupportedException異常。如下代碼所示:
class ConcretePrototype implements Cloneable { …… public Prototype clone() { Object object = null; try { object = super.clone(); } catch (CloneNotSupportedException exception) { System.err.println("Not support cloneable"); } return (Prototype )object; } …… } |
在用戶端建立原型對象和複製對象也很簡單,如下代碼所示:
Prototype obj1 = new ConcretePrototype(); Prototype obj2 = obj1.clone(); |
一般而言,Java語言中的clone()方法滿足:
(1)
對任何對象x,都有x.clone() != x,即複製對象與原型對象不是同一個對象;
(2)
對任何對象x,都有x.clone().getClass() == x.getClass(),即複製對象與原型對象的類型一樣;
(3)
如果對象x的equals()方法定義恰當,那麼x.clone().equals(x)應該成立。
為了擷取對象的一份拷貝,我們可以直接利用Object類的clone()方法,具體步驟如下:
(1)
在衍生類別中覆蓋基類的clone()方法,並聲明為public;
(2)
在衍生類別的clone()方法中,調用super.clone();
(3)衍生類別需實現Cloneable介面。
此時,Object類相當於抽象原型類,所有實現了Cloneable介面的類相當於具體原型類。
【作者:劉偉http://blog.csdn.net/lovelion】