裝飾者(Decorator)
1.1.1 摘要
裝飾者模式:動態地給一個對象添加一些額外的職責,就增加功能來說,Decorator模式比生成子類更為靈活。
Decorator模式的工作原理是:可以創建始于Decorator對象(負責新的功能的對象)終于原對象的一個對象“鏈”。

圖1裝飾者鏈
裝飾者模式隱含的是通過一條條裝飾鏈去實現具體對象,每一條裝飾鏈都始于一個Componet對象,每個裝飾者對象后面緊跟著另一個裝飾者對象,而對象鏈終于ConcreteComponet對象。
關鍵字:提高內聚性,面向接口編程,封裝變化使用組合,OCP
使用率:
medium
圖2裝飾者模式
1.1.2參與類或接口作用
ConcreteComponent:讓Decorator對象為自己添加功能。有時候使用ConcreteComponent的派生類提供核心功能,在這種情況就是用ConcreteComponent替代了Component的功能,而且裝飾者是繼承于ConcreteComponent的子類。
Component:定義ConcreteComponent和Decorator類要實現的方法,簡單來說如果一個類繼承于該類就具有裝飾或被裝飾能力。
Decorator:具有特定裝飾功能的類,用來裝飾ConcreteComponent類。
1.1.3裝飾者的實現
裝飾者模式顧名思義就是用來裝飾物件的,其實像我們生活中禮品的裝飾就是將一個禮物包裹的更加賞心悅目,更符合要送給對象和心意。OK現在想想更加接近編程的例子,我想大家都了解IP報文,IP報文是通過很多報頭組成的,而且每個報頭都有其作用(例如:數據包的目的地址和源地址)。每次當我們要傳輸數據的時候,我們電腦將根據TCP/IP報文格式去裝飾我們要傳輸的數據,好啦裝飾者模式出現啦。

圖3裝飾者實現
現在我們定義了IPMsg類,它是我們要發送的數據在發送過程我們可以隨意添加報頭(注意:IP報文格式是固定),但數據本身毫不關心究竟要添加什么報頭和怎樣添加,SourAddress和DestAddress是我們的裝飾者負責給我們數據添加報頭,裝飾者只關心自己添加的功能,并不關心其他裝飾者的實現。
Decorator模式幫助我們將問題分為兩部分:
-
如何實現提供新功能對象。
-
如何為每種特殊情況組織對象。
通過上面的UML圖我們可以發現,和我們前面介紹裝飾者模式結構是一模一樣,但我們實現設計模式的過程要重視的不是模式而是設計原則。
/// <summary>
/// 抽象的Component。
/// </summary>
abstract public class Component
{
abstract public void addMessage();
}
abstract public class MessageDecorator : Component
{
//為了實現簡單,并沒有使用屬性
private Component component;
public MessageDecorator()
{
}
public MessageDecorator(Component component)
{
this.component = component;
}
}
public class IPMsg : Component
{
public override void addMessage()
{
////要被裝飾數據。
Console.Write("Data->");
}
}
/// <summary>
/// 裝飾者
/// </summary>
public class SourAddress : MessageDecorator
{
private Component component;
public SourAddress(Component component)
{
this.component = component;
}
public override void addMessage()
{
this.component.addMessage();
Console.Write("SourAddress(Decorator)->");
}
}
/// <summary>
/// 裝飾者
/// </summary>
public class DestAddress : MessageDecorator
{
private Component component;
public DestAddress(Component component)
{
this.component = component;
}
public override void addMessage()
{
this.component.addMessage();
Console.Write("DestAddress(Decorator)->");
}
}
為了簡單描述出裝飾者模式基本結構,我按照裝飾者模式定義去實現,但在現實的編程中并不是我們每次實現裝飾者模式都應該按照以上結構,往往沒有按照以上的結構來實現,而是根據實際情況做出改變,OK接下來我們介紹一個變形的裝飾者模式。
1.1.4控件中的裝飾者
我相信大家都有用過PS,而且都了解過PS中圖層的原理,當我們P圖片時候使用圖層可以實現很多效果,而且如果我們覺得其中一個圖層效果不好看我們可以直接刪除,而不影響其他圖層,我們每次P出來的照片都是一層層圖層的疊加得到的。OK我們再見裝飾者模式啦。
通過一個簡單的WinForm例子的實現給圖片加邊框和打標簽的程序來介紹一下裝飾者模式的變形。
圖4圖層中的裝飾者
現在我們裝飾者模式發生了變化,原來的Component類被ConcreteComponent類替代了,而且裝飾者成為了ConcreteComponent的子類。也許有人問這還是裝飾者模式嗎?這不是簡單的繼承嗎?其實我覺得模式的實現不能給那些條條框框給限制住了,更重要的是具體設計具體模式嘛。
/// <summary>
/// ConcreteComponent.
/// </summary>
public partial class Photo : Form
{
protected Image image;
protected Point point;
/// <summary>
/// Initializes a new instance of the <see cref="Photo"/> class.
/// </summary>
public Photo()
{
InitializeComponent();
this.image = new Bitmap("Rush.jpg");
this.Text = "DecoratorDemo";
this.Height = 200;
this.Width = 300;
this.Paint += new PaintEventHandler(CustomDrawer);
this.point.X = this.Width / 2 - this.image.Width / 2;
this.point.Y = this.Height / 2 - this.image.Height;
}
/// <summary>
/// Customs the drawer.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.Forms.PaintEventArgs"/> instance containing the event data.</param>
public virtual void CustomDrawer(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(this.image, this.point.X, this.point.Y);
}
}
我們先定義一個Photo類,它繼承于Form然后添加虛方法CustomDrawer,每當發生重繪時候都調用CustomDrawer方法,該方法給Form添加一張圖片,Photo類并不關心它如何被裝飾和如何實現裝飾,它只管把圖片顯示出來就好了。
/// <summary>
/// The decorator.
/// </summary>
public class Bordered : Photo
{
protected Photo photo;
private Color color;
private int borderSize;
public Bordered()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Bordered"/> class.
/// </summary>
/// <param name="photo">The photo.</param>
/// <param name="color">The color.</param>
/// <param name="borderSize">Size of the border.</param>
public Bordered(Photo photo, Color color, int borderSize)
{
this.photo = photo;
this.color = color;
this.borderSize = borderSize;
this.point.X -= this.borderSize;
this.point.Y -= this.borderSize;
}
/// <summary>
/// Customs the drawer.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.Forms.PaintEventArgs"/> instance containing the event data.</param>
public override void CustomDrawer(object sender, PaintEventArgs e)
{
photo.CustomDrawer(sender, e);
e.Graphics.DrawRectangle(new Pen(this.color, this.borderSize), this.point.X, this.point.Y,this.image.Width, this.image.Height);
}
}
/// <summary>
/// The decorator.
/// </summary>
class Tagged : Photo
{
Photo photo;
string tag;
int number;
public Tagged()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Tagged"/> class.
/// </summary>
/// <param name="photo">The photo.</param>
/// <param name="tag">The tag.</param>
public Tagged(Photo photo, string tag)
{
this.photo = photo;
this.tag = tag;
}
/// <summary>
/// Customs the drawer.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.Forms.PaintEventArgs"/> instance containing the event data.</param>
public override void CustomDrawer(object sender, PaintEventArgs e)
{
photo.CustomDrawer(sender, e);
Random random = new Random();
e.Graphics.DrawString(this.tag, new Font("Arial", 16),
new SolidBrush(Color.Goldenrod), new PointF(random.Next(this.photo.Width), random.Next(this.photo.Height)));
}
}
然后我們分別定義兩個裝飾者類Bordered和Tagged,它通過重寫CustomDrawer方法給照片添加一個框和標簽,這就是裝飾的實現,而且它們只關心自己裝飾的實現從而提高了裝飾類的內聚性。
////客戶端實現
////只用標簽裝飾
photo = new Tagged(photo, this.txtTag.Text.Trim());
////只用照片框裝飾
photo = new Bordered(photo, Color.Blue, Convert.ToInt32(this.comBorderSize.Text));
////先用照片框再用標簽裝飾
bordered = new Bordered(photo, Color.Blue, Convert.ToInt32(this.comBorderSize.Text));
photo = new Tagged(bordered, this.txtTag.Text.Trim());


圖5客戶端UI
通過上面的客戶端UI發現,客戶端負責的主要職能是使用哪些裝飾過程去裝飾我們的照片,而裝飾者只負責自己的裝飾功能提高它的內聚性。其實在.NET3.0中的控件類System.Windows.Controls就是用了裝飾者模式實現BorderStyle或ViewBox等效果。


圖6裝飾效果

圖7組合裝飾效果

圖8 .NET控件中裝飾者模式
1.1.5 I/O中的裝飾者
通過上面的例子可以發現裝飾者模式在UI的動態顯示效果的作用,而且把要裝飾過程都分配給客戶端實現,裝飾者只關心自己的裝飾功能。而大部分介紹裝飾者模式主要以I/O設計為例子。
沒錯裝飾者模式的一個常見應用場合是I/O API的設計,這里我們以.NET中的I/O設計為例,現在讓我們看一下.NET中的I/O類。
System.IO.Stream
System.IO.BufferedStream
System.IO.FileStream
System.IO.MemoryStream
System.Net.Sockets.NetworkStream
System.Security.Cryptography.CryptoStream

圖9 .NET I/O設計
在學習設計模式之后能更加清晰理解I/O類之間的關系和作用,我講一下我對I/O Stream的理解:數據就像流水一樣在流動,而我們充當著管道工人的工作,要通過使用不同種類和數量不一的管子(不同I/O類)把水輸送到千家萬戶。
現在我們通過一個簡單的數據加密來介紹I/O中的裝飾者模式。
首先分別定義輸入輸出流,然后我們再定義一個數據加密流,接著我們對輸出流進行裝飾(加密)。
fin = new FileStream(inFile, FileMode.Open, FileAccess.Read);
fout = new FileStream(encFile,FileMode.OpenOrCreate, FileAccess.Write);
CryptoStream fenc = new CryptoStream(fout, des3.CreateEncryptor(key, IV),CryptoStreamMode.Write);


圖10 明文加密成功
////以下是完整的加密代碼
class Program
{
public static readonly int MAXSIZE = 100;
/// <summary>
/// Mains the specified args.
/// </summary>
/// <param name="args">The args.</param>
static void Main(string[] args)
{
////Get fully qualified file names.
////Due to we have only one Module, we can also use "GetModule" method.
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName);
path = path.Substring(0, path.IndexOf(@"\bin") + 1);
string inFile = path + "InFile.txt";
string outFile = path + "UndecoratedOutFile.txt";
string encFile = path + "DecoratedOutFile.txt";
string decFile = path + "DecoratorDecFile.txt";
FileStream fin = new FileStream(inFile, FileMode.Open, FileAccess.Read);
FileStream fout = new FileStream(outFile, FileMode.OpenOrCreate, FileAccess.Write);
fout.SetLength(0);
int readLent = 0;
long totLen = fin.Length;
byte[] bin = new byte[MAXSIZE];
int len = 0;
//// Read and write the txt file operation.
while (readLent < totLen)
{
len = fin.Read(bin, 0, MAXSIZE);
fout.Write(bin, 0, MAXSIZE);
readLent += len;
}
fout.Close();
fin.Close();
Console.WriteLine("Created undecorated file.\n");
fin = new FileStream(inFile, FileMode.Open, FileAccess.Read);
fout = new FileStream(encFile,FileMode.OpenOrCreate, FileAccess.Write);
fout.SetLength(0);
TripleDESCryptoServiceProvider des3 = new TripleDESCryptoServiceProvider();
byte[] key = HexToBytes("EA81AA1D5FC1EC53E84F30AA746139EEBAFF8A9B76638895");
byte[] IV = HexToBytes("87AF7EA221F3FFF5");
//// Use "CryptoStream" to decorate our output stream.
Console.WriteLine("Now we decorate output stream with CryptoStream...\n");
//// Stream was decorated by CryptoStream.
CryptoStream fenc = new CryptoStream(
fout, des3.CreateEncryptor(key, IV), CryptoStreamMode.Write);
readLent = 0;
while (readLent < totLen)
{
len = fin.Read(bin, 0, MAXSIZE);
fenc.Write(bin, 0, MAXSIZE);
readLent += len;
}
fin.Close();
fout.Close();
//fenc.Close();
Console.WriteLine("Created decorated file.\n");
Console.ReadKey();
}
public static byte[] HexToBytes(string hex)
{
byte[] bytes = new byte[hex.Length / 2];
for (int i = 0; i < hex.Length / 2; i++)
{
string code = hex.Substring(i * 2, 2);
bytes[i] = byte.Parse(code, System.Globalization.NumberStyles.HexNumber);
}
return bytes;
}
1.1.6 總結
裝飾者模式的應用場景:
1、 想透明并且動態地給對象增加新的職責的時候。
2、 給對象增加的職責,在未來存在增加或減少可能。
3、 用繼承擴展功能不太現實的情況下,應該考慮用組合的方式。
裝飾者模式的優點:
1、 通過組合而非繼承的方式,實現了動態擴展對象的功能的能力。
2、 有效避免了使用繼承的方式擴展對象功能而帶來的靈活性差,子類無限制擴張的問題。
3、 充分利用了繼承和組合的長處和短處,在靈活性和擴展性之間找到完美的平衡點。
4、 裝飾者和被裝飾者之間雖然都是同一類型,但是它們彼此是完全獨立并可以各自獨立任意改變的。
5、 遵守大部分GRASP原則和常用設計原則,高內聚、低偶合。
裝飾者模式的缺點:
1、 裝飾鏈不能過長,否則會影響效率。
2、 因為所有對象都是繼承于Component,所以如果Component內部結構發生改變,則不可避免地影響所有子類(裝飾者和被裝飾者),也就是說,通過繼承建立的關系總是脆弱地,如果基類改變,勢必影響對象的內部,而通過組合(Decoator HAS A Component)建立的關系只會影響被裝飾對象的外部特征。
3、只在必要的時候使用裝飾者模式,否則會提高程序的復雜性,增加系統維護難度。
|
|
關于作者:[作者]:
JK_Rush從事.NET開發和熱衷于開源高性能系統設計,通過博文交流和分享經驗,歡迎轉載,請保留原文地址,謝謝。 |

摘要:今天是母親節,首先祝愿天下母親節日快樂身體健康,來讓我們學習裝飾者來表達對母親的一份愛吧!裝飾者模式:動態地給一個對象添加一些額外的職責,就增加功能來說,Decorator模式比生成子類更為靈活。
Decorator模式的工作原理是:可以創建始于Decorator對象(負責新的功能的對象)終于原對象的一個對象“鏈”。
浙公網安備 33010602011771號