Design Patterns: Solidify Your C# Application Architecture with Design Patterns中文版(中篇)<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
作者:Samir Bajaj
譯者:榮耀
【譯序:C#進階文章。譯者對Samir提供的C#例子進行了簡單整理(作者提供的某些代碼在譯者的環境中無法通過編譯),並編寫了對應的C++示例,一併置於譯註中,以便讀者比對。譯文中所有C#、C++程序調試環境均爲Microsoft Visual Studio.NET 7.0 Beta2】
decorator
客戶應用常常需要加強某些類的方法所提供的服務。可能需要分別在方法調用前後插入一些預先處理和後繼處理的代碼。要實現這個目標,一種辦法是乾脆做成不同的方法調用。然而,這種方式不但麻煩,而且不利於框架的擴展。例如,如果對於不同的客戶來說明顯要執行不同的預先處理和後繼處理任務,應用程序邏輯將會因爲條件語句而變得晦澀不清並難以維護。問題是如何才能增強類所提供的功能的同時又不影響客戶代碼,而Decorator模式正是所需。
讓我們來考察一個具有遠程文件傳輸功能的類的例子。這樣的類代碼可能如表5所示。
表5 class FileTransfer { public virtual void Download(string url, byte[] data, int size) { // 下載文件 } public virtual void Upload(string url, byte[] data, int size) { // 上傳文件 } } |
假定有一個客戶程序對這個功能感興趣。除了能夠上傳和下載文件外,客戶應用還希望能夠將所有的文件傳輸請求和執行訪問檢查寫入日誌。基於decorator模式的一種實現方式是從FileTransfer類派生出一個類並重載虛方法,並於調用基類方法之前或之後,插入附加代碼。如表6所示。
表6 class Decorator : FileTransfer { private FileTransfer ft = new FileTransfer(); private bool IsAccessAllowed(string url) { bool result = true; // 決定是否對請求的URL訪問授權 return result; } private void LogAccess(string url) { // 將URL、時間、用戶身份等信息寫入數據庫 Console.WriteLine("Logging access to {0}", url); } public override void Download(string url, byte[] data, int size) { if (!IsAccessAllowed(url)) return; ft.Download(url, data, size); LogAccess(url); } } |
客戶程序可以繼續使用同樣的接口【譯註:並非C#語義的接口】進行工作。實際上,還可以進一步改進這個方案,可將FileTransfer類和Decorator類改爲實現同一個具有Upload和Download方法的接口的類。如此,就可以使客戶程序只依照接口工作,而同具體實現徹底解耦。
當一定範圍的擴展和任務可以在現有類的基礎上進行,並且將所有擴展都定義爲類是不切實際的情況下,使用decorator模式可以動態、透明地添加或移去功能而不會影響客戶代碼。
【譯註:以下是decorator模式完整示例
C#示例:
using System;
class FileTransfer
{
public virtual void Download(string url, byte[] data, int size)
{
// 下載文件
}
public virtual void Upload(string url, byte[] data, int size)
{
// 上傳文件
}
}
class Decorator : FileTransfer
{
private FileTransfer ft = new FileTransfer();
private bool IsAccessAllowed(string url)
{
bool result = true;
// 決定是否對請求的URL訪問授權
return result;
}
private void LogAccess(string url)
{
// 將URL、時間、用戶身份等信息寫入數據庫
Console.WriteLine("Logging access to {0}", url);
}
public override void Download(string url, byte[] data, int size)
{
if (!IsAccessAllowed(url)) return;
ft.Download(url, data, size);
LogAccess(url);
}
public override void Upload(string url, byte[] data, int size)
{
if (!IsAccessAllowed(url)) return;
ft.Upload(url, data, size);
LogAccess(url);
}
}
class Application
{
public static void Main()
{
Console.Write("Enter URL to access: ");
string url = Console.ReadLine();
Console.Write("Enable logging and access check? ");
string input = Console.ReadLine();
char ch = char.Parse(input);
bool decoration = (ch == 'y' || ch == 'Y');
FileTransfer ft = null;
if (!decoration)
ft = new FileTransfer();
else
ft = new Decorator();
byte[] buf = new byte[1024];
ft.Download(url, buf, 1024);
}
}
/*以下是某次運行時輸出結果:
Enter URL to access: www.csdn.net
Enable logging and access check? Y
Logging access to www.csdn.net
*/
C++示例:【譯註:下例中之所以混用std::string和byte數組只是爲了便於和C#程序比對而已】
#include "stdafx.h";
#include <iostream>
#include <string>
using namespace std;
typedef unsigned char byte;
class FileTransfer
{
public:
virtual void Download(string url, byte data[], int size)
{
// 下載文件
}
virtual void Upload(string url, byte data[], int size)
{
// 上傳文件
}
};
class Decorator : public FileTransfer // decorated file transfer
{
private:
FileTransfer* ft;
bool IsAccessAllowed(string url)
{
bool result = true;
// 決定是否對請求的URL訪問授權
return result;
}
void LogAccess(string url)
{
// 將URL、時間、用戶身份等信息寫入數據庫
cout<<"Logging access to "<<url<<endl;
}
public:
Decorator()
{
ft = new FileTransfer();
}
~Decorator()
{
if (ft)
{
delete ft;
ft = NULL;
}
}
void Download(string url, byte data[], int size)
{
if (!IsAccessAllowed(url)) return;
ft->Download(url, data, size);
LogAccess(url);
}
void Upload(string url, byte data[], int size)
{
if (!IsAccessAllowed(url)) return;
ft->Upload(url, data, size);
LogAccess(url);
}
};
int _tmain(int argc, _TCHAR* argv[])
{
cout<<"Enter URL to access: ";
string url;
cin>>url;
cout<<"Enable logging and access check? Type y or Y to continue: ";
char ch;
cin>>ch;
bool decoration = (ch == 'y' || ch == 'Y');
FileTransfer* ft = NULL;
if (!decoration)
ft = new FileTransfer();
else
ft = new Decorator();
byte* buf = new byte[1024];
ft->Download(url, buf, 1024);
delete []buf;
if (ft != NULL)
{
delete ft;
ft = NULL;
}
return 0;
}
/*以下是某次運行時輸出結果:
Enter URL to access: www.csdn.net
Enable logging and access check? Y
Logging access to www.csdn.net
*/
】
composite
當需要以一致的方式處理聚集對象和個體對象時,composite模式就派上了用場。【譯註:此“聚集”並非COM語義的聚集】一個常見的例子是列舉文件夾內容。文件夾可能不單包括文件,也可能有子文件夾。遞歸列舉某個頂層文件夾的應用可以使用條件語句來區分文件和子文件夾,並可通過遍歷目錄樹來打印出子文件夾中的所有文件。對該問題的一個更好的解決方案是使用composite模式。使用這種方式,文件夾內的每一種項目,不管是文件、子文件夾、網絡打印機或任何一種目錄元素的別名,都是遵從同樣接口的某個類的實例,該接口提供某個方法來描述這些元素的用戶友好的名稱。如此,客戶應用就不必區分每一種不同的元素,這也降低了應用邏輯的複雜性。
另一個例子,也是我下面要用C#語言展現的,是一個畫圖應用。它從對象數據庫中提取基本的和組合的圖形元素,並將它們畫在畫布上。假定數據庫可以容納Line、Circle和Drawing(包容有Line和Circle)。讓我們看看如表7所示的接口。
表7 interface Shape { void Draw(); } |
接口Shape有一個方法Draw。諸如Line之類的簡單圖形對象可以實現該接口,重載方法Draw【譯註:對於C#中的interface及其實現,並不需要virtual或override關鍵字,故與其說是“重載”,不如說是實現】,以在畫布上畫線。參見表8。
表8 class Line : Shape { private double x1, y1, x2, y2; public Line(double x1, double y1, double x2, double y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } public void Draw() { // 從(x1, y1) 到(x2, y2)畫一條線 } } |
Circle類和Line類類似。爲了能夠一致地處理聚集類和簡單實體類,聚集對象也應該實現Shape接口。Drawing是圖形對象的集合類,它實現了Draw方法,列舉出其容納的所有基本圖形對象並將它們一一畫出。表9的代碼展示了其工作原理。
表9 class Drawing : Shape { private ArrayList shapes; public Drawing() { shapes = new ArrayList(); } public void Add(Shape s) { shapes.Add(s); } public void Draw() { IEnumerator enumerator = shapes.GetEnumerator(); while (enumerator.MoveNext()) ((Shape) enumerator.Current).Draw(); } } |
注意,客戶無需關心某個圖形對象屬於基本類型和還是集合類型。橫跨於它們之上的公共接口【譯註:此處即是Shape】使得我們可以向其中添加新對象【譯註:新類型的對象】而不會對客戶程序產生任何影響。
通過公共接口,composite模式避免了客戶程序必須區分個體對象和容器對象,這在相當大的程度上促進了代碼重用並簡化了客戶程序邏輯。
注意,Drawing類的Draw方法使用了System.Collections名字空間中的類。如欲瞭解類庫更多知識,請參閱.NET Framework SDK的有關文檔。
【譯註:以下是composite模式完整示例
C#示例:
using System;
using System.Collections;
interface Shape
{
void Draw();
}
class Line : Shape
{
private double x1, y1, x2, y2;
public Line(double x1, double y1, double x2, double y2)
{
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
public void Draw()
{
//從(x1, y1) 到(x2, y2)畫一條線
Console.WriteLine("Drawing a line");
}
}
class Circle : Shape
{
private double x, y, r;
public Circle(double x, double y, double radius)
{
this.x = x;
this.y = y;
this.r = r;
}
public void Draw()
{
//以(x, y)爲圓心,r爲半徑畫一個圓
Console.WriteLine("Drawing a circle");
}
}
class Drawing : Shape
{
private ArrayList shapes;
public Drawing()
{
shapes = new ArrayList();
}
public void Add(Shape s)
{
shapes.Add(s);
}
public void Draw()
{
IEnumerator enumerator = shapes.GetEnumerator();
while (enumerator.MoveNext())
((Shape) enumerator.Current).Draw();
}
}
class Application
{
public static void Main()
{
Shape[] array = new Shape[3];
array[0] = new Line(0, 0, 10, 12);
array[1] = new Circle(2, 3, 5.5);
Drawing dwg = new Drawing();
dwg.Add(new Line(3, 4, 3, 5));
dwg.Add(new Circle(5, 6, 7.7));
array[2] = dwg;
// 畫出所有的圖形,注意:用一致的方式來訪問所有對象
for (int i = 0; i < 3; ++i)
array[i].Draw();
}
}
/*以下是程序輸出結果:
Drawing a line
Drawing a circle
Drawing a line
Drawing a circle
*/