.net 源码中的 设计模式

本来,看了一些设计模式的书。理解不深刻。纸上谈兵而已。我想,既然设计模式这么有用.NET  源码里,也应该有设计模式吧。网上真有这类文章。
原文:http://www.cnblogs.com/zhongkeruanjian/archive/2006/03/17/351964.html

整理如下:


最近,Microsoft越来越强调设计模式。如果你不熟悉设计模式,那么你会被这些新的术语和从来没有看见到的UML图所淹没。这次对设计模式的强调,相对于词汇的变化来讲,在方法论上并没有太多的变换,因为Microsoft® .NET Framework base class library (BCL)已经在设计模式上已经有了很广泛的应用。您其实已经接触到了这些模式,只是你没有意识到而已。在这篇文章里,我将会阐述一下几种基本模式的大概情况以及在BCLframework中其他方面是如何使用到他们的。看了这些后,你会发现Framework为什么会这么设计的动机,并且能对设计模式的抽象概念有一个直观的了解。

 

这里我提及到的模式大部分都是来源于由Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides撰写的《设计模式》这本书。他们就是四人帮。其实,四人帮并没有发明这些模式,但是他们把自软件开发以来别人已经做出来的好的设计加以文档化和正式化,从而形成设计模式。

 

如果你已经很熟悉设计模式,那么你在读这篇文章时会轻松自如,因为这些章节都是独立的,彼此之间没有什么太多的联系。有关于ASPNET 章节要求你要对请求管道熟悉,在那些章节里,我们会对请求管道有些大概的了解。

 

.NET Framework里,有些模式实在是太普遍了,它们常常会内置在程序语言里,而不仅仅在类库中体现。我将要讨论的前两个模式----Observer(观察者)Iterator(迭代器)模式,都已经有C#VBNET的语言特性所支持。就像你将要看到的一样,他们是很多常规的编程工作主要的部分。

迭代器模式(Iterator Pattern)

         许多编程的任务都是操作集合。无论是简单的对象列表还是复杂的B-Tree集合,我们都需要经常访问集合里的元素,我们经常通过一些方法来访问这些集合中的元素,例如从前到后,从后到前,或者正序或者倒序访问。
         一个最常见的基本方法之一就是将对象列表存储的数组里,数组类型已经内置在VB.NET与C#语言里了,它们都有对数组元素进行枚举的循环结构:在C#里是Foreach,在VB.NET里是For Eeah。这里有一个枚举数组的例子:

int[] values = new int[] {12345}
foreach(int i in values)

   Console.Write(i.ToString() 
+ " "); 
}

这些语句会在内部使用一个迭代器,你所能知道的是确信能将数组中的元素在每一次循环中枚举出来。      
        为了让这些语句能正常执行,在表达式中数组对象必须实现IEnumerable,所有实现了IEnumerable对象集合类都能够遍历(也就是枚举),IEnumerable接口有一个方法:GetEnumerator,这个方法能返回一个实现了IEnumerator的类型,这个实现了IEnumerator的类型能够对这个集合进行枚举。它有一个Current的属性,并且有能够从头到尾的向前枚举集合中元素的方法—MoveNext和Rest,这些对象集合类都System.Collections名称空间里,这些集合类和数组一样,实现了IEnumerable接口凭此来枚举集合中的元素。 
        如果你查看一下C#编译器生成的使用Foreach语句的中间语言MSIL(Microsoft intermediate language),你会发现大多数的集合类都采用枚举器来进行枚举。下面有一个使用 IEnumerator方式来进行枚举的例子,它与前面的那个例子实现了同样的功能: 

int[] values = new int[] {12345}
IEnumerator e 
= ((IEnumerable)values).GetEnumerator(); 
while(e.MoveNext()) 

    Console.Write(e.Current.ToString() 
+ " "); 
}
 

         .NET Framework使用IEnumerable和IEnumerator来应用迭代器模式,迭代器模式让你很轻松的遍历一个集合而不需要集合来展示自己内部的处理过程。实现了IEnumerator的迭代类从实现了IEnumerable的集合类中分离开来,这个类负责来处理枚举的状态(包括怎么表达当前数组中的元素和如何迭代集合中的元素),并把枚举的算法包含其中。这种方法能让你同时有几个用不同的方法枚举集合的迭代器,而不需要给集合添加任何的复杂性!
 

观察者模式

 

Observer :观察者,需要目标对象在发现变化时能获得通知并调用本身某个更新的方法。

Subejct : 目标,即当本身发现变化时能通知每一个观察者。

 

    好的 OO 设计不但强调封装而且还强调松耦合,换句话说,类要尽可能的封装自己内部的私有细节并且应该将类与类之间的强依赖减小到最少。在大多数应用里,类并不是孤立的,它们之间往往会相互交互和影响。这里有一个很普遍的类与类交互场景:当一个对象( subject )发生改变时,另外一个对象( Observer )要求能收到这个 (subject 改变的 ) 通知。例如:几个窗体控件需要在一个按钮点击后能够改变它们的呈现。一个简单的解决方法就是在 Subject 对象状态改变时直接去调用 Observer 的某一个特定的方法。这种方法将会引入一大把问题:

Subject 需要知道将要调用 Observer 哪一个方法,这种情况将会导致 Subject 与某个特定的 Observer 紧耦合。此外,如果你需要添加另外几个 Observer ,你不得不在 Subject 里添加调用每一个 Observer 方法的代码。如果 Observer 的数量是动态变化的,那么这将更加复杂,你将倒在这种无法维护的混乱之中。

       一个有效的解决方法是使用观察者模式。使用这种方法,你能够将 Subject 与它的那些 Observer 解耦以便能在设计时和运行时轻松的添加和删除任意数量的 Observer Subject 维护着一个 Observer 的列表,每当 Subject 的状态有所改变时,它能够依次通知列表中所有的 Observer 。请看如下的示例代码:

public abstract class CanonicalSubjectBase 

private ArrayList _observers = new ArrayList(); 
public void Add(ICanonicalObserver o) 
    

       _observers.Add(o); 
}
 

public void Remove(ICanonicalObserver o) 
    

        _observers.Remove(o); 
    }
 

public void Notify() 
    

        
foreach(ICanonicalObserver o in _observers) 
        

            o.Notify(); 
        }
 
    }
 
}
 

public interface ICanonicalObserver 

    
void Notify(); 
}
 

    所有的扮演 Observer 角色的类都实现了 ICanonicalObserver 接口,所有的 Subject 都必须继承自 CanonicalSubjectBase ,如果有一个新的 Observer 想监测 Object ,那么只需要添加此 Observer 的方法而不需要更改 Subject 中的任何一行代码! 而且你会发现 Object 再也不用直接依赖于那些 Observer (因为开始的那个方式是 Object 必须调用每个 Observer 的某个方法,译者注)了,而是仅仅依赖于 ICanonicalObserver 接口。

        尽管四人帮的观察者模式能够解决一些问题,但是这个方式还是有些缺陷,因为 Subejct 必须继承自 CanonicalSubjectBase ,所有的 Observer 必须实现 ICanonicalObserver 接口。(也就是说从一种意义上的紧耦合转移成了另一种意义上的耦合,译者注) , 让我们回头看看那个窗体按钮控件的例子吧,解决的方案就呈现在我们眼前: NET Framework 使用委托和事件来解决这个问题!如果你曾经在 ASP.NET WinForm 中写过程序,那么你可能已经使用过委托和事件,事件就是 Subejct ,而委托就是 Observer ,让我们来看看利用事件来实现的观察者模式的例子:

public delegate void Event1Hander(); 
public delegate void Event2Handler(int a); 
public class Subject 

    
public Subject(){} 
    
public Event1Hander Event1; 
    
public Event2Handler Event2; 
    
public void RaiseEvent1() 
  

     Event1Handler ev 
= Event1; 
     
if(ev != null) ev(); 
   }
 
   
public void RaiseEvent2() 
  

     Event2Handler ev 
= Event2; 
     
if (ev != null) ev(6); 
  }
 
}
 

public class Observer1 
{   
    
public Observer1(Subject s) 
    

        s.Event1 
+= new Event1Hander(HandleEvent1); 
        s.Event2 
+= new Event2Handler(HandleEvent2); 
}
 

public void HandleEvent1() 
    

      Console.WriteLine(
"Observer 1 - Event 1"); 
}
 
public void HandleEvent2(int a) 
    

        Console.WriteLine(
"Observer 1 - Event 2"); 
    }
 
}
 

     窗体按钮控件暴露了一个在按钮点击时能够通知 Observer 的点击事件,任何一个需要响应该事件的 Observer 仅仅只需要向这个事件注册一个委托( new Event1Hander method )便是一个委托,注册一个委托就是 += Event1 事件,译者注)。窗体按钮控件并不依赖于任何一个潜在的 Observer ,每一个 Observer 仅仅只需要知道这个事件的委托签名格式就 OK 了(在这个实例里委托就是 EventHandler ),因为 EventHandler 是一个委托类型而不是一个接口,所以每个 Observer 不需要去实现一个格外的接口,如果一个 Observer 已经有了一个符合委托签名格式的方法,那么它仅仅只需要把这个方法注册到 Subject 的事件中去。通过使用委托和事件,观察者模式能将 Subject Observer 解耦(也就是 Subject 不需要显式的去调用 Observer 的某一个方法)!

装饰模式(Decorator Pattern)

         有效的可执行程序不但包括读入与写出或者全部,无论读写的资源来源于哪里,资源都会被抽象的认作为字节流, .NET使用System.IO.Stream 类来表达这种抽象,不管它来源于文本文件,TCP/IP网络通信或者是其他的实体,文件数据类(FileStream)和网络通信类(NetworkStrem)都继承了Stream类,你可以轻松的处理这些数据而不管这些数据的来源,下面是一个将流中的字节输出到控制台的方法:

public static void PrintBytes(Stream s) 

    
int b; 
    
while((b = fs.ReadByte()) >= 0
    

        Console.Write(b 
+ " "); 
    }
 
}

       从流中一次读取一个字节不是一个非常有效的方式,例如:硬件能够(也是最适合的)连续的把数据块从磁盘中读到一个大的块中,如果你知道准备要读取一些字符,比较好的方法是将数据块一次性的从磁盘中取出后在内存中处理这些字符,在net Framework里,BufferedStream类负责处理这种操作,BufferedStream类的构造子有一个参数,这个参数可以是任何一个需要缓存处理的流对象, BufferedStream类重载了Stream的许多方法,比如Read和Write,凭此来提供更强大的功能(这个功能其实就是缓冲了,译者注)。因为 BufferedStream是Stream 类的子类,所以你可以像使用其它Stream类一样使用它。(值得注意的是FileStream已经内置了自己缓冲功能)
        同样的,你可以使用System.Security.Cryptography.CryptoStream类随意的对任何流对象进行加密和解密。下面的例子展示了使用几种不同类型的流对象来调用我的打印方法:

MemoryStream ms = new MemoryStream(new byte[] {12345678}); 

PrintBytes(ms); 

  BufferedStream buff 
= new BufferedStream(ms); 

PrintBytes(buff); 

buff.Close(); 

FileStream fs 
= new FileStream("http://www.cnblogs.com/decorator.txt", FileMode.Open); 

PrintBytes(fs); 

fs.Close(); 

这种通过组合方式无缝的,动态的给对象添加功能的能力就是装饰模式的一个例子。就像下图所展示的那样:

fig04.gif

         对于每一个Stream的实例,你可以通过将Stream包装成BufferedStream来给Stream来添加缓冲功能,而不需要改变数据的接口(就是Stream类型的接口,译者注),因为仅仅是组合对象(比如FileStream实例作为一个参数传给BufferedStream实例的构造子后,这个BufferedStream就是一个具有缓冲功能的FileStream实例,其他流实例也可以通过这种操作来扩展自己的功能,这就是装饰模式的精髓所在!译者注),这种方式比在编译时继承某一具体的流类型来得更加的优雅(比如,如果使用继承的方式,FileStream为了实现缓冲功能,而不得不有一个继承FileStream类的有缓冲功能的子类,这种方式的最大的问题是使整个继承树变得非常的臃肿,译者注),因为前一种方式可以在运行时实现!装饰模式核心的功能是所有的装饰者都实现了某接口或者重载了某一个抽象基类(比如Stream抽象基类)并提供了格外的功能。比如: BufferedStream重载了Read方法来提供从一个缓冲区读取的功能,而不是直接象其他流一样直接读取数据。就像前一个例子,任何一个组合成的装饰者,不管它有多么的复杂,还是和它的基类一样提供同样的功能。

附注:Net FrameWork中 System.IO.Stream的继承子类:

System.IO.Stream
      System.Data.OracleClient.OracleBFile
      System.Data.OracleClient.OracleLob

   System.IO.BufferedStream

   System.IO.FileStream

   System.IO.MemoryStream

   System.Net.Sockets.NetworkStream

   System.Security.Cryptography.CryptoStream

其中BufferedStream与CryptoStream就是装饰者模式中的装饰者:为其他Stream对象提供格外的功能。



另外,lovecherry 的贡献:
Builder Pattern:StringBuilder

Factory Method:IHttpHandlerFactory

Adapter Pattern:COM Interop、DataAdapter

Decorator Pattern:BufferedStream、CryptoStream

Composite Pattern:Enterprise Library2.0中的ObjectBuilder

Facade Pattern:Duwamish

Flyweight Pattern:.NET中的String类型

Template Method:服务器控件

Command Pattern:MVC

Iterator Pattern:IEnumerator

Oberver Pattern:委托和事件

Strategy Patterm:System.Configuration.Provider、IComparer

Singleton Pattern:Remoting的Singleton

原文地址:https://www.cnblogs.com/newsea/p/974630.html