——.NET設(shè)計(jì)模式系列之六
Terrylee , 2006 年 1 月
概述
在軟件系統(tǒng)中,有時(shí)候面臨的產(chǎn)品類是動(dòng)態(tài)變化的,而且這個(gè)產(chǎn)品類具有一定的等級(jí)結(jié)構(gòu)。這時(shí)如果用工廠模式,則與產(chǎn)品類等級(jí)結(jié)構(gòu)平行的工廠方法類也要隨著這種變化而變化,顯然不大合適。那么如何封裝這種動(dòng)態(tài)的變化?從而使依賴于這些易變對(duì)象的客戶程序不隨著產(chǎn)品類變化?
意圖
用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。
結(jié)構(gòu)圖
Prototype 模式結(jié)構(gòu)圖
生活中的例子
Prototype 模式使用原型實(shí)例指定創(chuàng)建對(duì)象的種類。新產(chǎn)品的原型通常是先于全部產(chǎn)品建立的,這樣的原型是被動(dòng)的,并不參與復(fù)制它自己。一個(gè)細(xì)胞的有絲分裂,產(chǎn)生兩個(gè)同樣的細(xì)胞,是一個(gè)扮演主動(dòng)角色復(fù)制自己原型的例子,這演示了原型模式。一個(gè)細(xì)胞分裂,產(chǎn)生兩個(gè)同樣基因型的細(xì)胞。換句話說(shuō),細(xì)胞克隆了自己。
使用細(xì)胞分裂例子的 Prototype 模式對(duì)象圖
原型模式解說(shuō)
我們考慮這樣一個(gè)場(chǎng)景,假定我們要開發(fā)一個(gè)調(diào)色板,用戶單擊調(diào)色板上任一個(gè)方塊,將會(huì)返回一個(gè)對(duì)應(yīng)的顏色的實(shí)例,下面我們看看如何通過(guò)原型模式來(lái)達(dá)到系統(tǒng)動(dòng)態(tài)加載具體產(chǎn)品的目的。
很自然,我們利用 OO 的思想,把每一種顏色作為一個(gè)對(duì)象,并為他們抽象出一個(gè)公用的父類,如下圖:
實(shí)現(xiàn)代碼:
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" );
}
}
客戶程序需要某一種顏色的時(shí)候,只需要?jiǎng)?chuàng)建對(duì)應(yīng)的具體類的實(shí)例就可以了。但是這樣我們并沒(méi)有達(dá)到封裝變化點(diǎn)的目的,也許你會(huì)說(shuō),可以使用工廠方法模式,為每一個(gè)具體子類定義一個(gè)與其等級(jí)平行的工廠類,那么好,看一下實(shí)現(xiàn):
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í)現(xiàn)了這一步之后,可以看到,客戶程序只要調(diào)用工廠方法就可以了。似乎我們用工廠方法模式來(lái)解決是沒(méi)有問(wèn)題的。但是,我們考慮的僅僅是封裝了 new 變化,而沒(méi)有考慮顏色的數(shù)量是不斷變化的,甚至可能是在程序運(yùn)行的過(guò)程中動(dòng)態(tài)增加和減少的,那么用這種方法實(shí)現(xiàn),隨著顏色數(shù)量的不斷增加,子類的數(shù)量會(huì)迅速膨大,導(dǎo)致子類過(guò)多,顯然用工廠方法模式有些不大合適。
進(jìn)一步思考,這些 Color 子類僅僅在初始化的顏色對(duì)象類別上有所不同。添加一個(gè) ColorTool 這樣的類,來(lái)參數(shù)化的它的實(shí)例,而這些實(shí)例是由 Color 支持和創(chuàng)建的。我們讓 ColorTool 通過(guò)克隆或者拷貝一個(gè) Color 子類的實(shí)例來(lái)創(chuàng)建新的 Color ,這個(gè)實(shí)例就是一個(gè)原型。如下圖所示:
實(shí)現(xiàn)代碼:
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;
}
public override ColorPrototype Clone()
{
// 實(shí)現(xiàn)淺拷貝
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 );
}
}
}
現(xiàn)在我們分析一下,這樣帶來(lái)了什么好處?首先從子類的數(shù)目上大大減少了,不需要再為每一種具體的顏色產(chǎn)品而定一個(gè)類和與它等級(jí)平行的工廠方法類,而 ColorTool 則扮演了原型管理器的角色。再看一下為客戶程序的實(shí)現(xiàn):
class App
{
public static void Main( string [] args)
{
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();
}
}
可以看到,客戶程序通過(guò)注冊(cè)原型實(shí)例就可以將一個(gè)具體產(chǎn)品類并入到系統(tǒng)中,在運(yùn)行時(shí)刻,可以動(dòng)態(tài)的建立和刪除原型。 最后還要注意一點(diǎn),在上面的例子中,用的是淺表復(fù)制。如果想做深復(fù)制,需要通過(guò)序列化的方式來(lái)實(shí)現(xiàn)。 經(jīng)過(guò)了上面的分析之后,我們?cè)賮?lái)思考下面的問(wèn)題:
1 .為什么需要Prototype模式?
引入原型模式的本質(zhì)在于利用已有的一個(gè)原型對(duì)象,快速的生成和原型對(duì)象一樣的實(shí)例。你有一個(gè)A的實(shí)例a:A a = new A(); 現(xiàn)在你想生成和car1一樣的一個(gè)實(shí)例b,按照原型模式,應(yīng)該是這樣:A b = a .Clone(); 而不是重新再new一個(gè)A對(duì)象。通過(guò)上面這句話就可以得到一個(gè)和a一樣的實(shí)例,確切的說(shuō),應(yīng)該是它們的數(shù)據(jù)成員是一樣的。Prototype模式同樣是返回了一個(gè)A對(duì)象而沒(méi)有使用new操作。
2 .引入Prototype模式帶來(lái)了什么好處?
可以看到,引入Prototype模式后我們不再需要一個(gè)與具體產(chǎn)品等級(jí)結(jié)構(gòu)平行的工廠方法類,減少了類的構(gòu)造,同時(shí)客戶程序可以在運(yùn)行時(shí)刻建立和刪除原型。
3 .Prototype模式滿足了哪些面向?qū)ο蟮脑O(shè)計(jì)原則?
依賴倒置原則:上面的例子,原型管理器(ColorManager)僅僅依賴于抽象部分(ColorPrototype),而具體實(shí)現(xiàn)細(xì)節(jié)(ConcteteColorPrototype)則依賴與抽象部分(ColorPrototype),所以Prototype很好的滿足了依賴倒置原則。
通過(guò)序列化實(shí)現(xiàn)深拷貝
要實(shí)現(xiàn)深拷貝,可以通過(guò)序列化的方式。抽象類及具體類都必須標(biāo)注為可序列化的 [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;
}
public override ColorPrototype Clone( bool Deep)
{
if (Deep)
return CreateDeepCopy();
else
return (ColorPrototype) this . MemberwiseClone();
}
// 實(shí)現(xiàn)深拷貝
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();
public ColorPrototype this [ string name]
{
get
{
return (ColorPrototype)colors[name];
}
set
{
colors . Add(name, value );
}
}
}
class App
{
public static void Main( string [] args)
{
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í)現(xiàn)要點(diǎn)
1 .使用原型管理器,體現(xiàn)在一個(gè)系統(tǒng)中原型數(shù)目不固定時(shí),可以動(dòng)態(tài)的創(chuàng)建和銷毀,如上面的舉的調(diào)色板的例子。
2 .實(shí)現(xiàn)克隆操作,在 .NET 中可以使用 Object 類的 MemberwiseClone() 方法來(lái)實(shí)現(xiàn)對(duì)象的淺表拷貝或通過(guò)序列化的方式來(lái)實(shí)現(xiàn)深拷貝。
3 . Prototype 模式同樣用于隔離類對(duì)象的使用者和具體類型(易變類)之間的耦合關(guān)系,它同樣要求這些“易變類”擁有穩(wěn)定的接口。
效果
1 .它對(duì)客戶隱藏了具體的產(chǎn)品類,因此減少了客戶知道的名字的數(shù)目。
2 . Prototype 模式允許客戶只通過(guò)注冊(cè)原型實(shí)例就可以將一個(gè)具體產(chǎn)品類并入到系統(tǒng)中,客戶可以在運(yùn)行時(shí)刻建立和刪除原型。
3 .減少了子類構(gòu)造, Prototype 模式是克隆一個(gè)原型而不是請(qǐng)求工廠方法創(chuàng)建一個(gè),所以它不需要一個(gè)與具體產(chǎn)品類平行的 Creater 類層次。
4 . Portotype 模式具有給一個(gè)應(yīng)用軟件動(dòng)態(tài)加載新功能的能力。由于Prototype的獨(dú)立性較高,可以很容易動(dòng)態(tài)加載新功能而不影響老系統(tǒng)。
5 . 產(chǎn)品類不需要非得有任何事先確定的等級(jí)結(jié)構(gòu),因?yàn)?/span> Prototype 模式適用于任何的等級(jí)結(jié)構(gòu)
6 .Prototype模式的最主要缺點(diǎn)就是每一個(gè)類必須配備一個(gè)克隆方法。而且這個(gè)克隆方法需要對(duì)類的功能進(jìn)行通盤考慮,這對(duì)全新的類來(lái)說(shuō)不是很難,但對(duì)已有的類進(jìn)行改造時(shí),不一定是件容易的事。
適用性
在下列情況下,應(yīng)當(dāng)使用 Prototype 模式:
1 .當(dāng)一個(gè)系統(tǒng)應(yīng)該獨(dú)立于它的產(chǎn)品創(chuàng)建,構(gòu)成和表示時(shí);
2 .當(dāng)要實(shí)例化的類是在運(yùn)行時(shí)刻指定時(shí),例如,通過(guò)動(dòng)態(tài)裝載;
3 .為了避免創(chuàng)建一個(gè)與產(chǎn)品類層次平行的工廠類層次時(shí);
4 .當(dāng)一個(gè)類的實(shí)例只能有幾個(gè)不同狀態(tài)組合中的一種時(shí)。建立相應(yīng)數(shù)目的原型并克隆它們可能比每次用合適的狀態(tài)手工實(shí)例化該類更方便一些。
總結(jié)
Prototype 模式同工廠模式,同樣對(duì)客戶隱藏了對(duì)象的創(chuàng)建工作,但是,與通過(guò)對(duì)一個(gè)類進(jìn)行實(shí)例化來(lái)構(gòu)造新對(duì)象不同的是,原型模式是通過(guò)拷貝一個(gè)現(xiàn)有對(duì)象生成新對(duì)象的,達(dá)到了“ 隔離類對(duì)象的使用者和具體類型(易變類)之間的耦合關(guān)系”的目的。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
