** Design Patterns: Solidify Your C# Application Architecture with Design Patterns中文版(中篇) **
作者: 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
1<iostream>
2
3#include <string>
4
5using namespace std;
6
7typedef unsigned char byte;
8
9class FileTransfer
10
11{
12
13public:
14
15virtual void Download(string url, byte data[], int size)
16
17{
18
19// 下载文件
20
21}
22
23virtual void Upload(string url, byte data[], int size)
24
25{
26
27// 上传文件
28
29}
30
31};
32
33class Decorator : public FileTransfer // decorated file transfer
34
35{
36
37private:
38
39FileTransfer* ft;
40
41bool IsAccessAllowed(string url)
42
43{
44
45bool result = true;
46
47// 决定是否对请求的URL访问授权
48
49return result;
50
51}
52
53void LogAccess(string url)
54
55{
56
57// 将URL、时间、用户身份等信息写入数据库
58
59cout<<"Logging access to "<<url<<endl; (!isaccessallowed(url))="" (ft)="" byte="" data[],="" decorator()="" delete="" download(string="" filetransfer();="" ft="NULL;" ft-="" ft;="" if="" int="" public:="" return;="" size)="" url,="" void="" {="" }="" ~decorator()="">Download(url, data, size);
60
61LogAccess(url);
62
63}
64
65void Upload(string url, byte data[], int size)
66
67{
68
69if (!IsAccessAllowed(url)) return;
70
71ft->Upload(url, data, size);
72
73LogAccess(url);
74
75}
76
77};
78
79int _tmain(int argc, _TCHAR* argv[])
80
81{
82
83cout<<"Enter URL to access: ";
84
85string url;
86
87cin>>url;
88
89cout<<"Enable logging and access check? Type y or Y to continue: ";
90
91char ch;
92
93cin>>ch;
94
95bool decoration = (ch == 'y' || ch == 'Y');
96
97FileTransfer* ft = NULL;
98
99if (!decoration)
100
101ft = new FileTransfer();
102
103else
104
105ft = new Decorator();
106
107byte* buf = new byte[1024];
108
109ft->Download(url, buf, 1024);
110
111delete []buf;
112
113if (ft != NULL)
114
115{
116
117delete ft;
118
119ft = NULL;
120
121}
122
123return 0;
124
125}
126
127/*以下是某次运行时输出结果:
128
129Enter URL to access: www.csdn.net
130
131Enable logging and access check? Y
132
133Logging access to www.csdn.net
134
135*/
136
137】
138
139** composite **
140
141当需要以一致的方式处理聚集对象和个体对象时,composite模式就派上了用场。【译注:此“聚集”并非COM语义的聚集】一个常见的例子是列举文件夹内容。文件夹可能不单包括文件,也可能有子文件夹。递归列举某个顶层文件夹的应用可以使用条件语句来区分文件和子文件夹,并可通过遍历目录树来打印出子文件夹中的所有文件。对该问题的一个更好的解决方案是使用composite模式。使用这种方式,文件夹内的每一种项目,不管是文件、子文件夹、网络打印机或任何一种目录元素的别名,都是遵从同样接口的某个类的实例,该接口提供某个方法来描述这些元素的用户友好的名称。如此,客户应用就不必区分每一种不同的元素,这也降低了应用逻辑的复杂性。
142
143另一个例子,也是我下面要用C#语言展现的,是一个画图应用。它从对象数据库中提取基本的和组合的图形元素,并将它们画在画布上。假定数据库可以容纳Line、Circle和Drawing(包容有Line和Circle)。让我们看看如表7所示的接口。
144
145表 7
146
147interface Shape
148
149{
150
151void Draw();
152
153}
154
155---
156
157接口Shape有一个方法Draw。诸如Line之类的简单图形对象可以实现该接口,重载方法Draw【译注:对于C#中的interface及其实现,并不需要virtual或override关键字,故与其说是“重载”,不如说是实现】,以在画布上画线。参见表8。
158
159表 8
160
161class Line : Shape
162
163{
164
165private double x1, y1, x2, y2;
166
167public Line(double x1, double y1, double x2, double y2)
168
169{
170
171this.x1 = x1; this.y1 = y1;
172
173this.x2 = x2; this.y2 = y2;
174
175}
176
177public void Draw()
178
179{
180
181// 从(x1, y1) 到(x2, y2)画一条线
182
183}
184
185}
186
187---
188
189Circle类和Line类类似。为了能够一致地处理聚集类和简单实体类,聚集对象也应该实现Shape接口。Drawing是图形对象的集合类,它实现了Draw方法,列举出其容纳的所有基本图形对象并将它们一一画出。表9的代码展示了其工作原理。
190
191表 9
192
193class Drawing : Shape
194
195{
196
197private ArrayList shapes;
198
199public Drawing()
200
201{
202
203shapes = new ArrayList();
204
205}
206
207public void Add(Shape s)
208
209{
210
211shapes.Add(s);
212
213}
214
215&nb
216
217---</url<<endl;></string></iostream>