概述
在軟件系統中,有時候面臨的產品類是動態變化的,而且這個產品類具有一定的等級結構。這時如果用工廠模式,則與產品類等級結構平行的工廠方法類也要隨著這種變化而變化,顯然不大合適。那麼如何封裝這種動態的變化?從而使依賴於這些易變對象的客戶程序不隨著產品類變化?
意圖
用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。
結構圖
Prototype模式結構圖
生活中的例子
Prototype模式使用原型實例指定創建對象的種類。新產品的原型通常是先於全部產品建立的,這樣的原型是被動的,並不參與復制它自己。一個細胞的有絲分裂,產生兩個同樣的細胞,是一個扮演主動角色復制自己原型的例子,這演示了原型模式。一個細胞分裂,產生兩個同樣基因型的細胞。換句話說,細胞克隆了自己。
使用細胞分裂例子的Prototype模式對象圖
原型模式解說
我們考慮這樣一個場景,假定我們要開發一個調色板,用戶單擊調色板上任一個方塊,將會返回一個對應的顏色的實例,下面我們看看如何通過原型模式來達到系統動態加載具體產品的目的。
很自然,我們利用OO的思想,把每一種顏色作為一個對象,並為他們抽象出一個公用的父類,如下圖:
實現代碼:
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");
}
}
客戶程序需要某一種顏色的時候,只需要創建對應的具體類的實例就可以了。但是這樣我們並沒有達到封裝變化點的目的,也許你會說,可以使用工廠方法模式,為每一個具體子類定義一個與其等級平行的工廠類,那麼好,看一下實現:
實現代碼:
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();
}
}
實現了這一步之後,可以看到,客戶程序只要調用工廠方法就可以了。似乎我們用工廠方法模式來解決是沒有問題的。但是,我們考慮的僅僅是封裝了new變化,而沒有考慮顏色的數量是不斷變化的,甚至可能是在程序運行的過程中動態增加和減少的,那麼用這種方法實現,隨著顏色數量的不斷增加,子類的數量會迅速膨大,導致子類過多,顯然用工廠方法模式有些不大合適。
進一步思考,這些Color子類僅僅在初始化的顏色對象類別上有所不同。添加一個ColorTool這樣的類,來參數化的它的實例,而這些實例是由Color支持和創建的。我們讓ColorTool通過克隆或者拷貝一個Color子類的實例來創建新的Color,這個實例就是一個原型。如下圖所示:
實現代碼:
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() {
//實現淺拷貝
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);
}
}
}
現在我們分析一下,這樣帶來了什麼好處?首先從子類的數目上大大減少了,不需要再為每一種具體的顏色產品而定一個類和與它等級平行的工廠方法類,而ColorTool則扮演了原型管理器的角色。再看一下為客戶程序的實現:
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(); }
}
可以看到,客戶程序通過注冊原型實例就可以將一個具體產品類並入到系統中,在運行時刻,可以動態的建立和刪除原型。最後還要注意一點,在上面的例子中,用的是淺表復制。如果想做深復制,需要通過序列化的方式來實現。經過了上面的分析之後,我們再來思考下面的問題:
1.為什麼需要Prototype模式?
引入原型模式的本質在於利用已有的一個原型對象,快速的生成和原型對象一樣的實例。你有一個A的實例a:A a = new A();現在你想生成和car1一樣的一個實例b,按照原型模式,應該是這樣:A b = a.Clone();而不是重新再new一個A對象。通過上面這句話就可以得到一個和a一樣的實例,確切的說,應該是它們的數據成員是一樣的。Prototype模式同樣是返回了一個A對象而沒有使用new操作。
2.引入Prototype模式帶來了什麼好處?
可以看到,引入Prototype模式後我們不再需要一個與具體產品等級結構平行的工廠方法類,減少了類的構造,同時客戶程序可以在運行時刻建立和刪除原型。
3.Prototype模式滿足了哪些面向對象的設計原則?
依賴倒置原則:上面的例子,原型管理器(ColorManager)僅僅依賴於抽象部分(ColorPrototype),而具體實現細節(ConcteteColorPrototype)則依賴與抽象部分(ColorPrototype),所以Prototype很好的滿足了依賴倒置原則。
通過序列化實現深拷貝
要實現深拷貝,可以通過序列化的方式。抽象類及具體類都必須標注為可序列化的[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();
}
//實現深拷貝
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(); }
}
實現要點
1.使用原型管理器,體現在一個系統中原型數目不固定時,可以動態的創建和銷毀,如上面的舉的調色板的例子。
2.實現克隆操作,在.NET中可以使用Object類的MemberwiseClone()方法來實現對象的淺表拷貝或通過序列化的方式來實現深拷貝。
3.Prototype模式同樣用於隔離類對象的使用者和具體類型(易變類)之間的耦合關系,它同樣要求這些“易變類”擁有穩定的接口。
效果
1.它對客戶隱藏了具體的產品類,因此減少了客戶知道的名字的數目。
2.Prototype模式允許客戶只通過注冊原型實例就可以將一個具體產品類並入到系統中,客戶可以在運行時刻建立和刪除原型。
3.減少了子類構造,Prototype模式是克隆一個原型而不是請求工廠方法創建一個,所以它不需要一個與具體產品類平行的Creater類層次。
4.Portotype模式具有給一個應用軟件動態加載新功能的能力。由於Prototype的獨立性較高,可以很容易動態加載新功能而不影響老系統。
5.產品類不需要非得有任何事先確定的等級結構,因為Prototype模式適用於任何的等級結構
6.Prototype模式的最主要缺點就是每一個類必須配備一個克隆方法。而且這個克隆方法需要對類的功能進行通盤考慮,這對全新的類來說不是很難,但對已有的類進行改造時,不一定是件容易的事。
適用性
在下列情況下,應當使用Prototype模式:
1.當一個系統應該獨立於它的產品創建,構成和表示時;
2.當要實例化的類是在運行時刻指定時,例如,通過動態裝載;
3.為了避免創建一個與產品類層次平行的工廠類層次時;
4.當一個類的實例只能有幾個不同狀態組合中的一種時。建立相應數目的原型並克隆它們可能比每次用合適的狀態手工實例化該類更方便一些。
總結
Prototype模式同工廠模式,同樣對客戶隱藏了對象的創建工作,但是,與通過對一個類進行實例化來構造新對象不同的是,原型模式是通過拷貝一個現有對象生成新對象的,達到了“隔離類對象的使用者和具體類型(易變類)之間的耦合關系”的目的。