.NET(C#) Internals: .NET Framework中已使用的设计模式
观察者模式(Observer Pattern)
观察者模式:在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常通过调用各观察者所提供的方法来实现。它的UML表示如下:
图1、观察者模式的UML表示 (来源:维基百科)
好的面向对象设计都强调封装(encapsulation)和松耦合(loose coupling)。换句话说,类应该保持内部细节私有并且最小化类之间严格的依赖关系。大部分应用程序,类并不是独立工作的,而是与其他类交互的。类交互的一个通常例子是:一个类应该(观察者,Observer)被通知,当被观察者(Subject)的某些东西改变了。例如,当单击一个按钮后可能某些Windows Forms的控件需要更新他们的显示。一个简单的解决方案是,当状态改变时让被观察者调用观察者特定的方法。但是,这回引入一连串的问题。因为被观察者需要知道调用哪个方法,这样就与特定观察者产生了紧耦合(tight coupling)。而且,如果当需要添加多个观察者时,不得不继续添加每个观察者方法调用的代码。如果观察者的数量动态地改变,这将变得更复杂。这将很难维护!
应用观察者模式能有效地解决这个问题。可以从观察者解耦被观察者,因此在设计时和运行时观察者可以容易地添加和移除。被观察者维护者一个对它感兴趣的观察者列表,每次被观察者的状态改变时,它对每个观察者调用Notify方法。下面这段代码展示了一个实现示例:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
所有的观察者类实现ICanonicalObserver接口,所有的被观察者必须继承自CanonicalSubjectBase。如果一个新的观察者想监视被观察者,Add方法可以轻松的处理而不必改变被观察者类的代码。注意:每个被观察者仅仅直接依赖于ICanonicalObserver接口,而不是特定的观察者。
然而使用GOF的观察者模式解决这些问题仍有一些障碍,因为被观察者必须继承一个特定的基类且观察者必须实现一个特定接口。考虑回Windows Forms按钮的例子,.NET Framework引入了委托和事件来解决这些问题。如果你已经编写过ASP.NET或Windows Forms程序,你可能就是有了事件和事件处理器。事件作为被观察者,然而委托作为观察者。下面代码展示了使用事件的观察者模式:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
Windows Forms Button控件公开一个Click事件,当button被点击时产生。任何设计为响应这个事件的类仅需要用这个事件注册一个委托。Button类不依赖与任何潜在的观察者,并且每个观察者仅需要知道这个事件的委托的正确类型(这里是EventHandler)。因为EventHandler是一个委托类型而不是一个接口,每个观察者不需要实现一个额外的接口。假定它已经包含一个与签名兼容的方法,只需要用被观察者的事件注册方法。通过使用委托和事件,观察者模式使被观察者与观察者们之间解耦了。
2、迭代器模式(Iterator Pattern)
迭代器模式:它可以让使用者通过特定的接口巡访容器中的每一个元素而不用了解底层的实作。它的UML表示如下:
图2、迭代器模式的UML表示(来源:TerryLee的.NET 设计模式(18):迭代器模式(Iterator Pattern))
许多编程任务包括操作对象的集合。不管这些集合是简单的列表还是更复杂的,如二叉树,经常需要访问集合中的每个对象。事实上,根据集合可能有几种不同的访问每个对象的方法,诸如从前向后、从后向前、前序或后序。为了保持集合简单,遍历代码通常放在自己单独的类中。
存储一个对象列表的常用方法之一就是用数组。数组类型在Visual Basic.NET和C#中都是内置类型,他们都有一个循环结构用于在数组上迭代:foreach(C#)和For Each(Visual Basic.NET)。下面是一个在数组上进行迭代的简单例子:
| 1 2 3 4 5 6 |
|
这些语句在后台对数组使用了迭代器。我们需要知道的就是它保证了循环保证了对数组中的每个元素进行一次遍历。
为了使这些语句起作用,foreach表达式中涉及的对象必须实现了IEnumerable接口。任何实现了IEnumerable接口的对象集合都可以被遍历(枚举)。这个接口仅有一个方法GetEnumerator(),它返回一个实现了IEnumerable的对象。IEnumerator接口包含遍历迭代集合所需要的代码,它有一个属性Current标识当前对象、方法MoveNext()移到下一个对象、方法Reset()重新开始。System.Collections命名空间中所有的集合类,及数组,都实现了IEnumerable接口,因此能被迭代。
如果你测试了由C#编译器生成foreach的MSIL代码,你可以看到大部分情况它仅使用IEnumerator去做迭代(特定类型,如数组和字符串,由编译器特别处理)。下面代码展示用IEnumerator方法实现上例功能的代码:
| 1 2 3 4 5 6 |
|
.NET Framework使用IEnumerable和IEnumerator接口实现了迭代器模式。迭代器模式使我们能够轻松地遍历一个集合而不用了解集合内部的工作机制。一个迭代器类,实现了IEnumerator接口,是一个独立与集合的类,实现了IEnumerable接口。迭代器类维护遍历的状态(包括当前元素是哪个和是否有更多的元素要遍历)。这个遍历的算法也包含在迭代器类中。这种方法可以同时有几个迭代器,每个以不同的方式遍历同一个集合,而不会对集合类增加任何复杂。
3、装饰模式(Decorator Pattern)
装饰模式:一种动态地往一个类中添加新的行为的设计模式,通过使用修饰模式,可以在运行时扩充一个类的功能。原理是:增加一个修饰类包裹原来的类,包裹的方式一般是通过在将原来的对象作为修饰类的构造函数的参数。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。修饰类必须和原来的类有相同的接口。UML表示如下:
图3、装饰模式UML表示(来源:TerryLee的.NET 设计模式(10):装饰模式(Decorator Pattern))
任何有用的可执行程序包括读取输入或写输出,或者都有。尽管数据源被读或写,能够把它们抽象地看成字节序列。.NET使用System.IO.Stream类去表示这个抽象。不管这些数据是包含在文本文件中字符,还是TCP/IP网络流的数据,或任何其他实体中,你将通过一个Stream访问它们。因为用于文件数据的类(FileStream)和用于网络流的类(NetWorkStream)都继承自Stream,你可以简单地编写独立于数据源的代码处理数据。下面的代码展示从一个Stream中打印字节到控制台:
| 1 2 3 4 5 6 7 8 |
|
每次读取单个字节通常不是最高效的访问流的方法。例如,硬件驱动器有能力(且优化了)从磁盘的一大块中读取连续的数据块。如果你知道你将读取几个字符,最好一次从磁盘中读取一个块然后从内存中逐字节地使用。框架包括BufferedStream类就是做这个的。BufferedStream的构造函数以流的类型为参数,设定你想缓存访问的类型。BufferedStream重写了Stream的主要方法,诸如Read和Write,提供更多的功能。因为它仍然是Stream的子类,你可以想其他Stream一样使用它(Note:FileStream包括他自己的缓存能力)。类似地,你可以使用System.Security.Cryptography.CryptoStream加密和解密流(Streams),除了它是一个流应用程序不需要知道任何其他的东西。下面展示了几种使用不同的Streams调用打印方法:
| 1 2 3 4 5 6 7 8 |
|
图4、使用装饰模式
更多推荐




所有评论(0)