NET Framework中已使用的设计模式.docx
《NET Framework中已使用的设计模式.docx》由会员分享,可在线阅读,更多相关《NET Framework中已使用的设计模式.docx(26页珍藏版)》请在冰豆网上搜索。
![NET Framework中已使用的设计模式.docx](https://file1.bdocx.com/fileroot1/2023-5/20/eb0e143b-829b-41d8-a343-2b63e33152d0/eb0e143b-829b-41d8-a343-2b63e33152d01.gif)
NETFramework中已使用的设计模式
.NET(C#)Internals:
.NETFramework中已使用的设计模式
2010-05-2920:
01by吴秦,2234visits,网摘,收藏,编辑
——适合有一定设计模式基础和.NET基础的人阅读。
写在前面
“设计模式”我一向是敬而远之的态度,不会去写这方面的文章,原因有二:
第一,要想写好设计模式的文章太难,需要笔者丰富的经验;第二,没有深厚的功底写出的设计模式文章容易误导他人。
自认没有深厚的功底,但我不会为了设计模式而设计模式。
我想大部分人对设计模式的理解是不够深刻的,不然应用自如,特别是初学者!
所有研究高质量的源码或框架是我们学习实践设计模式的好途径之一。
而我之所以写这篇文章,主要是因为它从.NETFramework入手介绍已经使用的设计模式,作为一个.NET开发人员应该再熟悉不过了,能够有比较深刻的认识和理解。
本文从.NETFramework中入手,发掘在.NETFramework中如何使用设计模式的。
从中我们知道我们平时使用.NET时,我们使用了那些模式及学习使用设计模式。
本文意译自DiscovertheDesignPatternsYou'reAlreadyUsinginthe.NETFramework及加入了相关设计模式的UML表示和主要介绍。
主要内容如下:
∙.NETFramework中使用的观察者模式(ObserverPattern)
∙.NETFramework中使用的迭代器模式(IteratorPattern)
∙.NETFramework中使用的装饰模式(DecoratorPattern)
∙.NETFramework中使用的适配器模式(AdapterPattern)
∙.NETFramework中使用的工厂模式(FactoryPattern)
∙.NETFramework中使用的策略模式(StrategyPattern)
∙ASP.NET中的组合模式(CompositePattern)
∙.NETFramework中使用的模板方法模式(TemplateMethodPattern)
∙ASP.NET管道中的模式(PatternsintheASP.NETPipeline)
o截取过滤器模式(InterceptingFilterPattern)
o页面控制器模式(PageControllerPattern)
oASP.NET中的其它web表示模式(OtherWebPresentationPatternsinASP.NET)
∙总结
1、观察者模式(ObserverPattern)
观察者模式:
在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。
这通常通过调用各观察者所提供的方法来实现。
它的UML表示如下:
图1、观察者模式的UML表示(来源:
维基百科)
好的面向对象设计都强调封装(encapsulation)和松耦合(loosecoupling)。
换句话说,类应该保持内部细节私有并且最小化类之间严格的依赖关系。
大部分应用程序,类并不是独立工作的,而是与其他类交互的。
类交互的一个通常例子是:
一个类应该(观察者,Observer)被通知,当被观察者(Subject)的某些东西改变了。
例如,当单击一个按钮后可能某些WindowsForms的控件需要更新他们的显示。
一个简单的解决方案是,当状态改变时让被观察者调用观察者特定的方法。
但是,这回引入一连串的问题。
因为被观察者需要知道调用哪个方法,这样就与特定观察者产生了紧耦合(tightcoupling)。
而且,如果当需要添加多个观察者时,不得不继续添加每个观察者方法调用的代码。
如果观察者的数量动态地改变,这将变得更复杂。
这将很难维护!
应用观察者模式能有效地解决这个问题。
可以从观察者解耦被观察者,因此在设计时和运行时观察者可以容易地添加和移除。
被观察者维护者一个对它感兴趣的观察者列表,每次被观察者的状态改变时,它对每个观察者调用Notify方法。
下面这段代码展示了一个实现示例:
viewsource
print?
01
publicabstractclassCanonicalSubjectBase
02
{
03
privateArrayList_observers=newArrayList();
04
publicvoidAdd(ICanonicalObservero)
05
{
06
_observers.Add(o);
07
}
08
publicvoidRemove(ICanonicalObservero)
09
{
10
_observers.Remove(o);
11
}
12
publicvoidNotify()
13
{
14
foreach(ICanonicalObserveroin_observers)
15
{
16
o.Notify();
17
}
18
}
19
}
20
21
publicinterfaceICanonicalObserver
22
{
23
voidNotify();
24
}
所有的观察者类实现ICanonicalObserver接口,所有的被观察者必须继承自CanonicalSubjectBase。
如果一个新的观察者想监视被观察者,Add方法可以轻松的处理而不必改变被观察者类的代码。
注意:
每个被观察者仅仅直接依赖于ICanonicalObserver接口,而不是特定的观察者。
然而使用GOF的观察者模式解决这些问题仍有一些障碍,因为被观察者必须继承一个特定的基类且观察者必须实现一个特定接口。
考虑回WindowsForms按钮的例子,.NETFramework引入了委托和事件来解决这些问题。
如果你已经编写过ASP.NET或WindowsForms程序,你可能就是有了事件和事件处理器。
事件作为被观察者,然而委托作为观察者。
下面代码展示了使用事件的观察者模式:
viewsource
print?
01
publicdelegatevoidEvent1Hander();
02
publicdelegatevoidEvent2Handler(inta);
03
04
publicclassSubject
05
{
06
publicSubject(){}
07
publicEvent1HanderEvent1;
08
publicEvent2HandlerEvent2;
09
publicvoidRaiseEvent1()
10
{
11
Event1Handlerev=Event1;
12
if(ev!
=null)ev();
13
}
14
15
publicvoidRaiseEvent2()
16
{
17
Event2Handlerev=Event2;
18
if(ev!
=null)ev(6);
19
}
20
}
21
22
publicclassObserver1
23
{
24
publicObserver1(Subjects)
25
{
26
s.Event1+=newEvent1Hander(HandleEvent1);
27
s.Event2+=newEvent2Handler(HandleEvent2);
28
}
29
publicvoidHandleEvent1()
30
{
31
Console.WriteLine("Observer1-Event1");
32
}
33
publicvoidHandleEvent2(inta)
34
{
35
Console.WriteLine("Observer1-Event2");
36
}
37
}
WindowsFormsButton控件公开一个Click事件,当button被点击时产生。
任何设计为响应这个事件的类仅需要用这个事件注册一个委托。
Button类不依赖与任何潜在的观察者,并且每个观察者仅需要知道这个事件的委托的正确类型(这里是EventHandler)。
因为EventHandler是一个委托类型而不是一个接口,每个观察者不需要实现一个额外的接口。
假定它已经包含一个与签名兼容的方法,只需要用被观察者的事件注册方法。
通过使用委托和事件,观察者模式使被观察者与观察者们之间解耦了。
2、迭代器模式(IteratorPattern)
迭代器模式:
它可以让使用者通过特定的接口巡访容器中的每一个元素而不用了解底层的实作。
它的UML表示如下:
图2、迭代器模式的UML表示(来源:
TerryLee的.NET设计模式(18):
迭代器模式(IteratorPattern))
许多编程任务包括操作对象的集合。
不管这些集合是简单的列表还是更复杂的,如二叉树,经常需要访问集合中的每个对象。
事实上,根据集合可能有几种不同的访问每个对象的方法,诸如从前向后、从后向前、前序或后序。
为了保持集合简单,遍历代码通常放在自己单独的类中。
存储一个对象列表的常用方法之一就是用数组。
数组类型在VisualBasic.NET和C#中都是内置类型,他们都有一个循环结构用于在数组上迭代:
foreach(C#)和ForEach(VisualBasic.NET)。
下面是一个在数组上进行迭代的简单例子:
viewsource
print?
1
int[]values=newint[]{1,2,3,4,5};
2
3
foreach(intiinvalues)
4
{
5
Console.Write(i.ToString()+"");
6
}
这些语句在后台对数组使用了迭代器。
我们需要知道的就是它保证了循环保证了对数组中的每个元素进行一次遍历。
为了使这些语句起作用,foreach表达式中涉及的对象必须实现了IEnumerable接口。
任何实现了IEnumerable接口的对象集合都可以被遍历(枚举)。
这个接口仅有一个方法GetEnumerator(),它返回一个实现了IEnumerable的对象。
IEnumerator接口包含遍历迭代集合所需要的代码,它有一个属性Current标识当前对象、方法MoveNext()移到下一个对象、方法Reset()重新开始。
System.Collections命名空间中所有的集合类,及数组,都实现了IEnumerable接口,因此能被迭代。
如果你测试了由C#编译器生成foreach的MSIL代码,你可以看到大部分情况它仅使用IEnumerator去做迭代(特定类型,如数组和字符串,由编译器特别处理)。
下面代码展示用IEnumerator方法实现上例功能的代码:
viewsource
print?
1
int[]values=newint[]{1,2,3,4,5};
2
IEnumeratore=((IEnumerable)values).GetEnumerator();
3
while(e.MoveNext())
4
{
5
Console.Write(e.Current.ToString()+"");
6
}
.NETFramework使用IEnumerable和IEnumerator接口实现了迭代器模式。
迭代器模式使我们能够轻松地遍历一个集合而不用了解集合内部的工作机制。
一个迭代器类,实现了IEnumerator接口,是一个独立与集合的类,实现了IEnumerable接口。
迭代器类维护遍历的状态(包括当前元素是哪个和是否有更多的元素要遍历)。
这个遍历的算法也包含在迭代器类中。
这种方法可以同时有几个迭代器,每个以不同的方式遍历同一个集合,而不会对集合类增加任何复杂。
3、装饰模式(DecoratorPattern)
装饰模式:
一种动态地往一个类中添加新的行为的设计模式,通过使用修饰模式,可以在运行时扩充一个类的功能。
原理是:
增加一个修饰类包裹原来的类,包裹的方式一般是通过在将原来的对象作为修饰类的构造函数的参数。
装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。
修饰类必须和原来的类有相同的接口。
UML表示如下:
图3、装饰模式UML表示(来源:
TerryLee的.NET设计模式(10):
装饰模式(DecoratorPattern))
任何有用的可执行程序包括读取输入或写输出,或者都有。
尽管数据源被读或写,能够把它们抽象地看成字节序列。
.NET使用System.IO.Stream类去表示这个抽象。
不管这些数据是包含在文本文件中字符,还是TCP/IP网络流的数据,或任何其他实体中,你将通过一个Stream访问它们。
因为用于文件数据的类(FileStream)和用于网络流的类(NetWorkStream)都继承自Stream,你可以简单地编写独立于数据源的代码处理数据。
下面的代码展示从一个Stream中打印字节到控制台:
viewsource
print?
1
publicstaticvoidPrintBytes(Streams)
2
{
3
intb;
4
while((b=fs.ReadByte())>=0)
5
{
6
Console.Write(b+"");
7
}
8
}
每次读取单个字节通常不是最高效的访问流的方法。
例如,硬件驱动器有能力(且优化了)从磁盘的一大块中读取连续的数据块。
如果你知道你将读取几个字符,最好一次从磁盘中读取一个块然后从内存中逐字节地使用。
框架包括BufferedStream类就是做这个的。
BufferedStream的构造函数以流的类型为参数,设定你想缓存访问的类型。
BufferedStream重写了Stream的主要方法,诸如Read和Write,提供更多的功能。
因为它仍然是Stream的子类,你可以想其他Stream一样使用它(Note:
FileStream包括他自己的缓存能力)。
类似地,你可以使用System.Security.Cryptography.CryptoStream加密和解密流(Streams),除了它是一个流应用程序不需要知道任何其他的东西。
下面展示了几种使用不同的Streams调用打印方法:
viewsource
print?
1
MemoryStreamms=newMemoryStream(newbyte[]{1,2,3,4,5,6,7,8});
2
PrintBytes(ms);
3
BufferedStreambuff=newBufferedStream(ms);
4
PrintBytes(buff);
5
buff.Close();
6
FileStreamfs=newFileStream("
7
PrintBytes(fs);
8
fs.Close();
图4、使用装饰模式
使用组合动态透明地附加新功能到对象的能力是装饰模式的例子,如上图所示。
给定任何Stream的的实例,你可以通过包装在一个BufferedStream中添加缓冲访问的能力,而不用改变接口数据。
因为你仅仅是组合了对象,这可以在运行时做,而不像继承是编译时决定的。
核心功能通过一个接口或者抽象类(如Stream)定义,所有装饰都派生自它。
这些装饰自己实现(或重写)接口(或抽象类)中的方法以提供额外的功能。
例如BufferedStream重写Read方法从缓冲中读取流,而不是直接从流中读取。
如上面的代码所示,任何装饰的组合,不管多复杂,可以像只有基类一样使用。
4、适配器模式(AdapterPattern)
适配器模式:
将一个类别的接口转接成用户所期待的。
一个适配使得因接口不兼容而不能在一起工作的类工作在一起,做法是将类别自己的接口包裹在一个已存在的类中。
有两类适配器模式:
∙对象适配器模式——在这种适配器模式中,适配器容纳一个它我包裹的类的实例。
在这种情况下,适配器调用被包裹对象的物理实体。
∙类适配器模式——这种适配器模式下,适配器继承自已实现的类(一般多重继承)。
他们的UML表示分别如下:
图5、对象适配器模式
图6、类适配器模式(来源:
TerryLee的.NET设计模式(8):
适配器模式(AdapterPattern))
.NETFramework的优势之一就是向后兼容性。
从基于.NET的代码可以轻松的访问旧的COM对象,反之亦然。
为了在项目中使用COM组件,必须在VisualStudio中通过添加引用对话框添加引用。
在后台VisualStudio.NET调用tlbimp.exe工具创建一个包含在interop程序集中的运行时可调用包装(RuntimeCallableWrapper,RCW)类。
一旦添加了引用(且interop程序集已经生产了),COM组件就可以像其它托管代码类一样使用。
如果你在看别人写的代码没有看到引用列表(且没有坚持类关联的元数据或他们的实现),你将不知道哪些类是用.NET的语言写的,哪些是COM组件。
使这种神奇发生的是RCM。
COM组件有不同的错误处理机制及使用不同的数据类型。
例如.NETFramework中的字符串使用System.String类,而COM可能使用BSTR。
当在.NET代码中用一个字符串调用COM组件时,你可以像其它托管代码方法一样传递一个System.String。
在RCW内部,在COM调用之前,这个System.String转换成COM组件期望的格式,就像BSTR。
类似地,COM组件的一个方法调用典型地返回HRESULT表示成功或失败。
当一个COM方法调用返回表示失败的HRESULT时,RCW转化成一个异常(默认情况下),因此它能像其他托管代码错误一样处理。
尽管它们的接口不一样,但还是允许托管类和COM组件交互,RCWs是适配器模式的例子。
适配器模式使一个接口适合另一个,COM并不理解System.String类,因此RCW适配器使其适应为可以理解的。
即使你不能改变旧的组件工作机制,你仍可以与它交互。
这种情况经常使用适配器。
适配器类本身包含了一个适配(Adaptee),将来自客户端的调用转化为合适的和顺序的调用。
虽然听起来像装饰模式,它们之间有几个关键的不同。
∙装饰模式中,组合的对象接口是相同的;适配器模式中,你可以改变整个接口。
∙适配器模式中,给他们定义了一个明确的序列,适配必须包含在适配器中;装饰模式中,装饰类不必知道它包装了1个或500个其它类,因为接口是相同的。
因此使用装饰模式对应用程序时透明的,然而使用适配器模式不是。
5、工厂模式(FactoryPattern)
工厂模式:
通过调用不同的方法返回需要的类,而不是去实例化具体的类。
对实例创建进行了包装。
工厂方法是一组方法,他们针对不同条件返回不同的类实例,这些类一般有共同的父类。
它的UML表示如下:
图7、工厂模式的UML表示(来源:
TerryLee的.NET设计模式(5):
工厂方法模式(FactoryMethod))
许多情况下,在框架(Framework)中你可以自己不调用一个struct或class的构造器而获取一个新的实例。
System.Convert类包含了一系列的静态方法向这样工作。
例如,将一个整数转换为布尔类型,你可以调用Convert.ToBollean并传递一个整数。
如果整数是一个非零值则这个方法的返回值是一个新的Boolean并设为“true”,否则是“false”。
Convert类为我们创建Boolean实例,并设为正确的值,其它的类型转换也是类似。
Int32和Double上的Parse方法返回这些对象的新实例并根据给定的字符串设置合适的值。
这种创建新对象实例的策略称为工厂模式。
不用调用对象的构造器,你可以要求对象工厂为你创建一个实例。
如此一来可以隐藏创建对象的复杂性(就像如何从一个字符串解析为一个Double值,Double.Parse)。
如果你想改变创建对象的细节,你仅需要改变工厂它本身,你不必修改每个调用构造器的地方。
这些类型转换方法是工厂模式的变种,因为在问题中你不必使用这个工厂创建对象。
一个更纯的工厂模式的例子是System.Net.WebRequest类,用来发出请求(request)和从Internet上的资源接受响应(response)。
FTP、HTTP和文件系统请求默认页支持。
为了创建一个请求,调用Create方法和传递一个URI。
Create方法决定合适的请求协议,且返回合适的WebRequest的子类:
HttpWebRequest、FtpWebRequest(.NETFramework2.0新增的)、FileWebRequest。
调用者不需要知道每个特定的协议,仅需知道如何调用工厂和使用WebRequest获取返回值。
如果URI从一个HTTP地址改变为FTP地址,代码根本不用改变。
这是工厂模式的另一种常用用法。
父类作为一个工厂,且根据客户端传递的参数返回特定派生类。
就像WebRequest例子,它隐藏了选择合适派生类的复杂性。
6、策略模式(StrategyPattern)
策略模式:
指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。
比如每个人都要“交个人所得税”,但是“在美国交个人所得税”和“在中国交个人所得税”就有不同的算税方法。
它的UML表示如下:
图8、策略模式的UML表示(来源:
维基百科)
数组(Array)和数组列表(ArrayList)都提供通过Sort方法对集合中的对象排序的功能。
实际上,ArrayList.Sort仅对隐含的数组调用Sort。
这些方法使用的是快速排序(QuickSor)算法。
默认,Sort方法使用IComparable实现每个元素之间的比较。
有时,使用不同的方法对