——探索設計模式系列之六
Terrylee,2006年1月
概述
在軟件系統中,有時(shí)候面臨的產(chǎn)品類(lèi)是動(dòng)態(tài)變化的,而且這個(gè)產(chǎn)品類(lèi)具有一定的等級結構。這時(shí)如果用工廠(chǎng)模式,則與產(chǎn)品類(lèi)等級結構平行的工廠(chǎng)方法類(lèi)也要隨著(zhù)這種變化而變化,顯然不大合適。那么如何封裝這種動(dòng)態(tài)的變化?從而使依賴(lài)于這些易變對象的客戶(hù)程序不隨著(zhù)產(chǎn)品類(lèi)變化?
意圖
用原型實(shí)例指定創(chuàng )建對象的種類(lèi),并且通過(guò)拷貝這些原型創(chuàng )建新的對象。
結構圖

Prototype模式結構圖
生活中的例子
Prototype模式使用原型實(shí)例指定創(chuàng )建對象的種類(lèi)。新產(chǎn)品的原型通常是先于全部產(chǎn)品建立的,這樣的原型是被動(dòng)的,并不參與復制它自己。一個(gè)細胞的有絲分裂,產(chǎn)生兩個(gè)同樣的細胞,是一個(gè)扮演主動(dòng)角色復制自己原型的例子,這演示了原型模式。一個(gè)細胞分裂,產(chǎn)生兩個(gè)同樣基因型的細胞。換句話(huà)說(shuō),細胞克隆了自己。

使用細胞分裂例子的Prototype模式對象圖
原型模式解說(shuō)
我們考慮這樣一個(gè)場(chǎng)景,假定我們要開(kāi)發(fā)一個(gè)調色板,用戶(hù)單擊調色板上任一個(gè)方塊,將會(huì )返回一個(gè)對應的顏色的實(shí)例,下面我們看看如何通過(guò)原型模式來(lái)達到系統動(dòng)態(tài)加載具體產(chǎn)品的目的。
很自然,我們利用OO的思想,把每一種顏色作為一個(gè)對象,并為他們抽象出一個(gè)公用的父類(lèi),如下圖:

實(shí)現代碼:
public abstract class Color
{
public abstract void Display();
}
public class RedColor:Color
{
public override void Display()
{
Console.WriteLine("Red‘s RGB Values are:255,0,0");
}
}
public class GreenColor:Color
{
public override void Display()
{
Console.WriteLine("Green‘s RGB Values are:0,255,0");
}
}
客戶(hù)程序需要某一種顏色的時(shí)候,只需要創(chuàng )建對應的具體類(lèi)的實(shí)例就可以了。但是這樣我們并沒(méi)有達到封裝變化點(diǎn)的目的,也許你會(huì )說(shuō),可以使用工廠(chǎng)方法模式,為每一個(gè)具體子類(lèi)定義一個(gè)與其等級平行的工廠(chǎng)類(lèi),那么好,看一下實(shí)現:

實(shí)現代碼:
public abstract class ColorFactory
{
public abstract Color Create();
}
public class RedFactory:ColorFactory
{
public override Color Create()
{
return new RedColor();
}
}
public class GreenFactory:ColorFactory
{
public override Color Create()
{
return new GreenColor();
}
}
實(shí)現了這一步之后,可以看到,客戶(hù)程序只要調用工廠(chǎng)方法就可以了。似乎我們用工廠(chǎng)方法模式來(lái)解決是沒(méi)有問(wèn)題的。但是,我們考慮的僅僅是封裝了new變化,而沒(méi)有考慮顏色的數量是不斷變化的,甚至可能是在程序運行的過(guò)程中動(dòng)態(tài)增加和減少的,那么用這種方法實(shí)現,隨著(zhù)顏色數量的不斷增加,子類(lèi)的數量會(huì )迅速膨大,導致子類(lèi)過(guò)多,顯然用工廠(chǎng)方法模式有些不大合適。
進(jìn)一步思考,這些Color子類(lèi)僅僅在初始化的顏色對象類(lèi)別上有所不同。添加一個(gè)ColorTool這樣的類(lèi),來(lái)參數化的它的實(shí)例,而這些實(shí)例是由Color支持和創(chuàng )建的。我們讓ColorTool通過(guò)克隆或者拷貝一個(gè)Color子類(lèi)的實(shí)例來(lái)創(chuàng )建新的Color,這個(gè)實(shí)例就是一個(gè)原型。如下圖所示:

實(shí)現代碼:
abstract class ColorPrototype
{
public abstract ColorPrototype Clone();
}
class ConcteteColorPrototype : ColorPrototype
{
private int _red, _green, _blue;
public ConcteteColorPrototype(int red, int green, int blue)
{
this._red = red;
this._green = green;
this._blue = blue;
}
{
//實(shí)現淺拷貝
return (ColorPrototype) this.MemberwiseClone();
}
public void Display(string _colorname)
{
Console.WriteLine("{0}‘s RGB Values are: {1},{2},{3}",
_colorname,_red, _green, _blue );
}
}
class ColorManager
{
Hashtable colors = new Hashtable();
public ColorPrototype this[string name]
{
get
{
return (ColorPrototype)colors[name];
}
set
{
colors.Add(name,value);
}
}
}
現在我們分析一下,這樣帶來(lái)了什么好處?首先從子類(lèi)的數目上大大減少了,不需要再為每一種具體的顏色產(chǎn)品而定一個(gè)類(lèi)和與它等級平行的工廠(chǎng)方法類(lèi),而ColorTool則扮演了原型管理器的角色。再看一下為客戶(hù)程序的實(shí)現:
class App
{
public static void
{
ColorManager colormanager = new ColorManager();
//初始化顏色
colormanager["red"] = new ConcteteColorPrototype(255, 0, 0);
colormanager["green"] = new ConcteteColorPrototype(0, 255, 0);
colormanager["blue"] = new ConcteteColorPrototype(0, 0, 255);
colormanager["angry"] = new ConcteteColorPrototype(255, 54, 0);
colormanager["peace"] = new ConcteteColorPrototype(128, 211, 128);
colormanager["flame"] = new ConcteteColorPrototype(211, 34, 20);
//使用顏色
string colorName = "red";
ConcteteColorPrototype c1 = (ConcteteColorPrototype)colormanager[colorName].Clone();
c1.Display(colorName);
colorName = "peace";
ConcteteColorPrototype c2 = (ConcteteColorPrototype)colormanager[colorName].Clone();
c2.Display(colorName);
colorName = "flame";
ConcteteColorPrototype c3 = (ConcteteColorPrototype)colormanager[colorName].Clone();
c3.Display(colorName);
Console.ReadLine();
}
}
可以看到,客戶(hù)程序通過(guò)注冊原型實(shí)例就可以將一個(gè)具體產(chǎn)品類(lèi)并入到系統中,在運行時(shí)刻,可以動(dòng)態(tài)的建立和刪除原型。最后還要注意一點(diǎn),在上面的例子中,用的是淺表復制。如果想做深復制,需要通過(guò)序列化的方式來(lái)實(shí)現。經(jīng)過(guò)了上面的分析之后,我們再來(lái)思考下面的問(wèn)題:
1.為什么需要Prototype模式?
引入原型模式的本質(zhì)在于利用已有的一個(gè)原型對象,快速的生成和原型對象一樣的實(shí)例。你有一個(gè)A的實(shí)例a:A a = new A();現在你想生成和car1一樣的一個(gè)實(shí)例b,按照原型模式,應該是這樣:A b = a.Clone();而不是重新再new一個(gè)A對象。通過(guò)上面這句話(huà)就可以得到一個(gè)和a一樣的實(shí)例,確切的說(shuō),應該是它們的數據成員是一樣的。Prototype模式同樣是返回了一個(gè)A對象而沒(méi)有使用new操作。
2.引入Prototype模式帶來(lái)了什么好處?
可以看到,引入Prototype模式后我們不再需要一個(gè)與具體產(chǎn)品等級結構平行的工廠(chǎng)方法類(lèi),減少了類(lèi)的構造,同時(shí)客戶(hù)程序可以在運行時(shí)刻建立和刪除原型。
3.Prototype模式滿(mǎn)足了哪些面向對象的設計原則?
依賴(lài)倒置原則:上面的例子,原型管理器(ColorManager)僅僅依賴(lài)于抽象部分(ColorPrototype),而具體實(shí)現細節(ConcteteColorPrototype)則依賴(lài)與抽象部分(ColorPrototype),所以Prototype很好的滿(mǎn)足了依賴(lài)倒置原則。

通過(guò)序列化實(shí)現深拷貝
要實(shí)現深拷貝,可以通過(guò)序列化的方式。抽象類(lèi)及具體類(lèi)都必須標注為可序列化的[Serializable],上面的例子加上深拷貝之后的完整程序如下:
using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable]
abstract class ColorPrototype
{
public abstract ColorPrototype Clone(bool Deep);
}
[Serializable]
class ConcteteColorPrototype : ColorPrototype
{
private int _red, _green, _blue;
public ConcteteColorPrototype(int red, int green, int blue)
{
this._red = red;
this._green = green;
this._blue = blue;
}
{
if(Deep)
return CreateDeepCopy();
else
return (ColorPrototype) this.MemberwiseClone();
}
//實(shí)現深拷貝
public ColorPrototype CreateDeepCopy()
{
ColorPrototype colorPrototype;
MemoryStream memoryStream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(memoryStream, this);
memoryStream.Position = 0;
colorPrototype = (ColorPrototype)formatter.Deserialize(memoryStream);
return colorPrototype;
}
public ConcteteColorPrototype Create(int red,int green,int blue)
{
return new ConcteteColorPrototype(red,green,blue);
}
public void Display(string _colorname)
{
Console.WriteLine("{0}‘s RGB Values are: {1},{2},{3}",
_colorname,_red, _green, _blue );
}
}
class ColorManager
{
Hashtable colors = new Hashtable();
{
get
{
return (ColorPrototype)colors[name];
}
set
{
colors.Add(name,value);
}
}
}
class App
{
public static void
{
ColorManager colormanager = new ColorManager();
//初始化顏色
colormanager["red"] = new ConcteteColorPrototype(255, 0, 0);
colormanager["green"] = new ConcteteColorPrototype(0, 255, 0);
colormanager["blue"] = new ConcteteColorPrototype(0, 0, 255);
colormanager["angry"] = new ConcteteColorPrototype(255, 54, 0);
colormanager["peace"] = new ConcteteColorPrototype(128, 211, 128);
colormanager["flame"] = new ConcteteColorPrototype(211, 34, 20);
//使用顏色
string colorName = "red";
ConcteteColorPrototype c1 = (ConcteteColorPrototype)colormanager[colorName].Clone(false);
c1.Display(colorName);
colorName = "peace";
ConcteteColorPrototype c2 = (ConcteteColorPrototype)colormanager[colorName].Clone(true);
c2.Display(colorName);
colorName = "flame";
ConcteteColorPrototype c3 = (ConcteteColorPrototype)colormanager[colorName].Clone(true);
c3.Display(colorName);
Console.ReadLine();
}
}
實(shí)現要點(diǎn)
1.使用原型管理器,體現在一個(gè)系統中原型數目不固定時(shí),可以動(dòng)態(tài)的創(chuàng )建和銷(xiāo)毀,如上面的舉的調色板的例子。
2.實(shí)現克隆操作,在.NET中可以使用Object類(lèi)的MemberwiseClone()方法來(lái)實(shí)現對象的淺表拷貝或通過(guò)序列化的方式來(lái)實(shí)現深拷貝。
3.Prototype模式同樣用于隔離類(lèi)對象的使用者和具體類(lèi)型(易變類(lèi))之間的耦合關(guān)系,它同樣要求這些“易變類(lèi)”擁有穩定的接口。
效果
1.它對客戶(hù)隱藏了具體的產(chǎn)品類(lèi),因此減少了客戶(hù)知道的名字的數目。
2.Prototype模式允許客戶(hù)只通過(guò)注冊原型實(shí)例就可以將一個(gè)具體產(chǎn)品類(lèi)并入到系統中,客戶(hù)可以在運行時(shí)刻建立和刪除原型。
3.減少了子類(lèi)構造,Prototype模式是克隆一個(gè)原型而不是請求工廠(chǎng)方法創(chuàng )建一個(gè),所以它不需要一個(gè)與具體產(chǎn)品類(lèi)平行的Creater類(lèi)層次。
4.Portotype模式具有給一個(gè)應用軟件動(dòng)態(tài)加載新功能的能力。由于Prototype的獨立性較高,可以很容易動(dòng)態(tài)加載新功能而不影響老系統。
5.產(chǎn)品類(lèi)不需要非得有任何事先確定的等級結構,因為Prototype模式適用于任何的等級結構
6.Prototype模式的最主要缺點(diǎn)就是每一個(gè)類(lèi)必須配備一個(gè)克隆方法。而且這個(gè)克隆方法需要對類(lèi)的功能進(jìn)行通盤(pán)考慮,這對全新的類(lèi)來(lái)說(shuō)不是很難,但對已有的類(lèi)進(jìn)行改造時(shí),不一定是件容易的事。
適用性
在下列情況下,應當使用Prototype模式:
1.當一個(gè)系統應該獨立于它的產(chǎn)品創(chuàng )建,構成和表示時(shí);
2.當要實(shí)例化的類(lèi)是在運行時(shí)刻指定時(shí),例如,通過(guò)動(dòng)態(tài)裝載;
3.為了避免創(chuàng )建一個(gè)與產(chǎn)品類(lèi)層次平行的工廠(chǎng)類(lèi)層次時(shí);
4.當一個(gè)類(lèi)的實(shí)例只能有幾個(gè)不同狀態(tài)組合中的一種時(shí)。建立相應數目的原型并克隆它們可能比每次用合適的狀態(tài)手工實(shí)例化該類(lèi)更方便一些。
總結
Prototype模式同工廠(chǎng)模式,同樣對客戶(hù)隱藏了對象的創(chuàng )建工作,但是,與通過(guò)對一個(gè)類(lèi)進(jìn)行實(shí)例化來(lái)構造新對象不同的是,原型模式是通過(guò)拷貝一個(gè)現有對象生成新對象的,達到了“隔離類(lèi)對象的使用者和具體類(lèi)型(易變類(lèi))之間的耦合關(guān)系”的目的。
參考文獻
《設計模式》(中文版)
《DesignPatternsExplained》
《Java與模式》(閻宏 著(zhù))
聯(lián)系客服