重温经典之《企业应用架构模式》——.NET中的架构模式运用 (Base Patterns 1)
在这本书的最后一章,Martin Fowler放了一大堆各种各样的模式,称之为“基本模式(Base Patterns)”,这些模式大多比较简单,专注于解决企业应用中的某个细节问题,可以说不大重要,但真的用到时候有确实那么有用。
因为模式简单,所以模式本身就不用花太多文字解释,我们的重点应该放在体会各种模式的异同。甚至有的模式,早已经成为了我们熟知的一种再平常不过的功能,而不需要再去认为其实一种“模式”了,从中,我着实体会到了模式的发展。
Gateway(入口)
每天的编程我们都会面对大量的API,在处处OO的今天直接面向API编程显然不是一个好的选择,于是我们就引入Gateway,其实很简单,就是把现有的API先封装一下,然后抽象出方便系统调用的接口,再使用。
反正,程序需要什么,我们就为Gateway类定义什么样的接口,至于如何实现,就完全不用管了。
如果你接触过GOF的面向对象设计模式,应该会发现这和另外三种模式很相像,它们分别是Façade, Adapter, Mediator,书中着重介绍了这几种的区别。我重点强调的是,这四种模式,都至少提供了对一个现有对象的再次封装,而区别微妙的体现在封装的目的上,而这种微妙的区别,大家得细细体会。
Gateway是在已经有了现成的服务类情况下,为了简化客户程序的编写,将服务类的提供的API接口进一步简化,一方面简化调用,另一方面方便测试;
这和Adapter非常相像,但是Adapter所做的工作仅仅是重新封装一个服务类使之能与另一个调用它的客户程序匹配,而Gateway并不包含客户类,因此从这个意义上来说,Gateway = 定义新的客户代码接口 + 进行Adapter
最后其实不管什么模式,都可以算作Façade,但是Façade着重在服务代码编写的时候考虑,重点在隐藏不公开的成员,暴露程序功能
而Mediator倒是比较清楚,Mediator模式中服务类和客户类都会调用新定义的中介类,这是和其他几个模式显然不同的地方。
要是你坚持看完了以上这些异同,估计你已经快晕过去了。其实说实话,在.NET+C#的平台这几种所谓的模式表现在代码上很可能是一模一样的,之所以要分成这么多种模式,完全是从模式的思想上来看的。
虽然学院派了一点,但我还是认为理解这几种模式对从更高层次上理解面向对象编程编程有很大的帮助。下面看看它们.NET框架里的应用。
System.IO.File类,负责磁盘文件的创建,删除等等工作,在Windows平台下,这些操作就是调用了Windows API的几个文件处理函数。总体上看来,.NET框架是利用Gateway模式封装了几个API函数,并作为File类中的成员公开出来。
使用Reflactor工具我们可以大概的看看.NET的File类是如何工作的,这里就以CreateFile方法为例,经过一次次的转到定义(提供的重载可真是多),最终我们看到这样一个复杂的方法(FileStream.Init),定义如下
internal unsafe void Init(string path, FileMode mode, FileAccess access, int rights, bool useRights, FileShare share, int bufferSize, FileOptions options, Win32Native.SECURITY_ATTRIBUTES secAttrs, string msgPath, bool bFromProxy)
这虽然已经是一个unsafe方法了,但依然是一个.NET下的类,再继续看下去,所有方法最终都会调用这样一个类:
internal static class Win32Native{
…
}
Win32Native这个类负责直接封装了所有的WindowsAPI,按照原来的样子提供了WindowsAPI的封装,其中用于创建文件的方法名叫Win32Native.SateCreateFile
真的有好多层封装,我们来看看一个简单的File.Create方法到底进行了多少调用(跳过方法重载):
File.Create -> FileStream.Init -> Win32Native.SafeCreateFile -> WindowsAPI
也许你平时知道.NET很多功能都是直接调用WindowsAPI,但是你可能没有想到在WindowsAPI上还有这么多层封装吧。为什么要有这么多层封装呢?
从Gateway模式的角度来看,每一层封装,都是对上一层简化,每一层都是上一层的Gateway。
Win32Native类,提供了dll文件导入,API函数映射到.NET函数功能;
映射出来的函数非常多,那就公开一个文件处理方面的封装吧,于是有了FileStream类,我想这叫做Façade可能更好一些;
我们可以看到Init函数非常复杂,到这为止仅仅只是简单的导出WindowsAPI的函数,使用起来仍然相当不方便,于是我们便加上一个Gateway,这个Gateway封装了所有unsafe的代码,处理了很多该处理的异常,最后甚至提供了一个最简单的重载File.Create(string fileName);
到此为止,创建文件这个功能封装完毕了,和使用WindownsAPI创建文件比起来,使用一个简单的File.Create来创建文件真的很完美。
Mapper(映射器)
Gateway模式中对象调用是单向的,Gateway所起的作用仅仅是对低层函数或者类的封装,而Mapper希望完成的事情远不止这些。Mapper模式的重点在于关注映射两种不同机制的系统,他希望通过一个中间的Mapper,完全隔离两个系统,是两者完全不互相依赖。
听得最多的,也许就是最有争议的ORM(Object Relational Mapping)。
ORM的话题总是说不完的,简单面向对象系统和关系数据库系统是两种完全不同的系统,但是我们又必须使用关系数据库系统存储数据,因此我们需要一种程序来进行面向对象数据和关系数据库中存储的数据之间的转换。
Mapper的作用就是处理这种转换,这个概念很早就被提出来,但是很模糊,因为他并没有说清楚这个“转换”到底如何设计,好多年前Martin Fowler就提出了Mapper,甚至提出了Data Mapper的简单实现,但是直到今天,在.NET框架下还是没有看到一个真正完美的ORM框架。
我们要理解的,是映射器这种思想,设计一种类,直接在两种不同的机制间进行转换,让双方都满意。这很像Adapter,其实我也不能很清楚的说出Adapter和Mapper的区别,可能他们的差别在于粒度上吧,Adapter在于两种类之间的接口转换,而Mapper不只是接口间的转换,他更在意两种完全不同实现机制之间的相互转换,这种转换的技术含量要高得多。
在.NET平台下,严格意义上说.ORM框架应该只有刚刚推出的ADO.net Entity Framework,这套框架就着重在于对面向对象设计中的各种继承组合关系到关系数据库的映射工作,当然它毕竟是刚推出的工具,我也没有多少实际使用经验,到底如何也不好说。
其他的ORM工具从开源NHibernate到NBear等等可以说多如牛毛,而MS官方的除了之前的DLINQ之外,其实以前的强类型Dataset都可以算作简单的ORM,至少是具有ORM概念的东西。
总的说来,ORM概念已经热了好几年了,但是依然还是很不成熟。
和ORM一样,我认为直到今天Mapper模式也尚未走向成熟,也许,随意的希望隔离两个本来就相关的子系统这种行为本身就有些不切实际。
Layer Supertype
一句话就可以说的非常清楚:如果多个类有同样的成员,那么建立一个基类,让所有的类继承这个基类。
我没什么好说的了,老外真的很喜欢总结,连这都能算作一种模式。
如果这真的算一种模式的话,在.NET框架中,这样的运用就是数不胜数了,几乎绝大多数继承关系的都可以算作这种模式,比如WinForm中TextBox和Button同样继承于Control类。
Separated Interface
并非每一种面向对象编程语言都有接口(Interface)这个概念的,但在像C#或者Java这样在语言级别提供了接口功能的语言中,接口的使用已经是司空见惯了。
要说明的是,实现Separated Interface并非一定要使用接口这东西,用抽象类也是可以很好的实现接口的。
为什么要用接口,大声喊出一句话,把声明和实现分开!
这里涉及到一些基础的东西了,有了抽象类,为什么还要有接口呢?
一个原因是类的继承关系仅仅只是一个面向对象系统中纵向的关系,而同一层次的类之间的关系则无法使用继承关系描述,除非,使用C++的多继承。
实践证明,多继承带来的问题远大于好处,因此便有了更注重功能定义的接口,这个概念。接口更重在描述体现类本身的功能,接口并不关心这个类“是”什么,他只关心这个类“能做什么”。
从这个意义上来说,接口定义了类之间横向的功能关系。这是另一种结构上优化。
纵向继承关系
在继承的基础上加上横向实现关系
IWork2也许并没有定义在ClassRoot中,但是因为接口的存在,本来没有关系的ClassB和Class3被建立了关系,这就是横向的实现关系。
更加重要的是,我们可以把IWork1和IWork2的实现放在其他的程序集或者包中,在我们发布的版本中,只需要指定我们所依赖的接口就足够了。这大大降低了各个程序集之间的耦合度。
分离接口在.NET框架中的运用也是遍地都是,也许你已经根本不认为只是一种模式了,而只是非常平常的使用它了。这便是模式的最高境界,也可见这种分离接口模式的成熟。
举个最典型例子,在定义WCF服务的过程中有一个步骤是定义WCF的ServiceContract, ServiceContract实际上就是一系列接口。在创建一个WCF服务时,通常会将所有的服务代码抽象成这样的接口,客户端只需获得这个接口,就能够进行远程调用服务器以使用。关于WCF的详细信息,可以参考MSDN,或相关资料。