很想写相关的内容,一直以来这方面的东西很杂,自己各方面都多多少少有些总结,但是没有系统的成文,始终觉得是个遗憾。
这是这个系列的第一篇。
本文说的架构,还并不是说的Tier层的架构,这里面不会涉及到分布式、缓存、网络结构等等的布局,而是集中在软件的内部,是代码层级的,考虑这点架构的点,目的是在于帮助我们写出清晰、易维护的软件。
关注点分离(Separation of concerns, SoC)
这个准则应该作为我们开发和架构的指导性的原则。在该原则下,软件应该按照其业务来将软件本身划分成不同的部分,从而进一步降低耦合性,不过,这感觉是句废话,大家好像都懂。
那么首先,关注点是什么呢?
比如说一组对代码有影响的业务逻辑,或对某个具体业务有影响的业务规则。它其实可以很通用,比如针对x86环境优化代码的细节;也可以很具体,比如某个将要初始化的类的名字,只要它对我们是有用的,我们就称它为其中的一个关注点。
举例来说,如果某个软件有个逻辑:是将某些产品高亮显示出来,以显示这些产品的独特性。
那么,把这些产品挑选出来的逻辑,应该和把这些产品做高亮的逻辑分离开来,这是两个不同的关注点(只是刚好这两个关注点是互相关联的而已)。
在架构上,如何去应用这条准则呢?比如说,把业务逻辑的行为分成基本的实现层(infrastruture)和UI层(理想的情况下,业务规则和业务逻辑都应该分离到不同的项目里面去,他们也不能互相产生依赖的关系)。这种结构能帮助我们保证业务逻辑更容易的测试和应用,而且在底层也没有互相耦合在一起。
关注点分离是我们对于软件分层的一个核心的考虑点。
把握好这个尺度,有助于我们建造模块化的应用程序。它的价值在于简化开发和提高维护性。这个准则做好了,各独立部分就能重用,也可以相对独立的开发和更新,某个模块更新了,其他的模块不必做额外的修改。
但是,听起来还是好抽象的感觉。
举例来说,ASP.NET MVC就是关注点分离的一个体现,它将原来的ASP.NET WebForm分离成模型(model)-视图(view)-控制器(controller),从而把业务逻辑、数据、界面分离,这也是组织代码结构的一个形式。
MVC的基本结构:
Model层表示应用程序的数据核心,通常负责在数据库中存取数据。
View是应用程序的显示层,通常是依据模型的数据而建立。
-
Controller是用来控制和处理输入输出的,是处理用户交互的部分,也负责向模型(Model层)发送数据。
MVC的这个设计各个关注点是分开的,这样有助于我们管理和开发复杂的应用程序,我们可以在某个时间点只集中精力在其中的某一个关注点,而不是所有的部分。举例来说,前端的开发人员可以配合设计团队绕过业务逻辑,专注在视图和交互设计部分。另外的一端,DBA也可以配合某个团队专注在数据持久化的部分,而中间的业务逻辑层又可以由其他团队集中精力来负责。这种分层也简化了分组开发,让测试也更为容易。
除了ASP.NET MVC还有其他的框架也是这样的关注点分离的思想,比如Django,Structs,Spring等等。
那么分层思想都有哪些方法呢,总不至于只是用个MVC就结束了吧?
其实东西还是比较多的,下面,将介绍一些分层的思想。
纵向分离
大家都懂,即便是最初级的程序员也都接触过,可能是你并没有意识到而已。
我们十好几年前的三层架构,界面层(UI Layer),业务逻辑层(Business Layer)和数据持久化层(Data Access Layer),就是这一种自上而下的纵向的分层手法。
横向分离
大家也懂。我们倡导的模块化的编程,把我们的软件拆分成模块或子系统。
从左到右是模块1、模块2、模块3,这是一种水平方向的切割。
这跟纵向的分离是两个不同的方向,横向分离大多是模块化的过程。
切面分离
有些内容是多个层之间都需要的,比如日志(logging),在你的系统里面,界面层、逻辑层、数据访问层可能都需要写日志,这种跨到多层同样逻辑就可以考虑切面分离。
在asp.net mvc中,我们可以使用filter来实现, Spring中也有SpringAOP等等。
依赖方向分离
我们考虑这几点:
- 有些类要修改的几率比其他的类修改的几率大得多。
- 具体的类比抽象类修改的几率大得多。
- 修改被依赖得很多的类可能引起很大的改动。
- 某些类比其他类被重用的可能性大得多。
依据这些考虑点,我们来决定某个类应该放在哪个层次里面,或者考虑将某一层切割成多层。
关注数据分离
在组织数据时,应该尽量考虑数据本身的固有属性,如果不是它们的固有属性,那么应该分离出来。
比如产品的类就不应该关联customer类,因为产品不应该跟客户直接产生数据关系,产品的颜色、型号、描述才是产品该有的固有属性。
至于客户,应该是用订单类来把他们联系在一起。
关注行为分离
跟上面讲的一样,行为也应该是事物或对象的固有的本身的行为,明显偏离原来行为的,应该考虑成另外的关注点儿分离开。
比如有一个函数叫做CreateNewCustomer(),那么CreateNewCustomer的行为就应该限定在创建一个新客户上面,给新客户自动发优惠券的动作就不能放到这个函数里面。
扩展分离
如果基于某种设计,原先不具有某些行为需要增加,可以考虑通过扩展或插件的形式来完成,将这些功能放入到插件或扩展中,就是扩展分离。
比如Firefox、Chrome的去广告的插件,这些功能增加了系统原本的行为,将这些行为分离到插件里面去,就是扩展分离。
委托分离
如果某个行为还无法具体确定,可以使用委托的方式。
比如C#的delegate,当我们还不知道某些具体行为应该如何实现,或者不应该在此处对该行为进行实现,或者有多个行为可以互相替代,就可以将函数的参数指定为一个delegate。
至于delegate具体怎样实现,那是其他部分应该关注的点。
比如现在需要将Customer的信息持久化,就可以把这个请求委托给DatabaseManager或WebSerivceManager,由他们自行处理数据,然后返回给我结果。
反转分离
现在有了很多的依赖注入的框架,像Autofac,Unit,Castle Windsor等等,这些帮助我们做依赖翻转,从而倒置依赖关系。
要指出是,上面提到了9种分离层次的概念,每一种概念都可以任意的与其他概念组合在一起,从而产生更多的变化。
在实际的开发过程中,没有东西是一成不变的,而层次和架构也应该是在开发的过程里面不断完善和重构。
初级程序员最烦的是需求或业务的修改,一些我们觉得奇奇怪怪的修改导致大家不断的修改代码,心里很烦,在心里也默默的把产品经理被翻过来倒过去骂了千百遍。
但是,在实际的工作中你会发现,软件开发就是这样,没有什么是不变的。
如果一定要找出一个不变的点,我想那应该是:
唯一不变的,就是变化。
关于不断的修改与重构,也可以参考《重构-改善既有代码的设计》,记得封面上写的是:
软件开发的不朽经典
生动阐述重构原理和具体做法
普通程序员进阶到编程高手必须修炼的秘笈
呃,再往下面离题越来越远了。
至此,关注点分离这块内容暂告一段落。