关注点分离是日常生活和生产中广泛使用的解决复杂问题的一种系统思维方法。

大体思路是,先将复杂问题做合理的分解,再分别仔细研究问题的不同侧面(关注点),最后综合各方面的结果,合成整体的解决方案。

在概念上分割整体以使实体个体化的观点可以追溯到柏拉图。柏拉图把探究自然比作在关节处切割自然,窍门在于要找到关节,不要像生疏的屠夫那样把关节切得粉碎。庄子在庖丁解牛寓言中也阐释了类似的真知灼见。

作为最重要的计算思维原则之一,关注点分离是计算科学和软件工程在长期实践中确立的一项方法论原则。此原则在业界更多的时候以分而治之 的面目出现,即将整体看成为部分的组合体并对各部分分别加以处理。模块化是其中最有代表性的具体设计原则之一

关注点分离(Separation of concerns,SOC)是对只与“特定概念、目标”(关注点)相关联的软件组成部分进行“标识、封装和操纵”的能力,即标识、封装和操纵关注点的能力。是处理复杂性的一个原则。由于关注点混杂在一起会导致复杂性大大增加,所以能够把不同的关注点分离开来,分别处理就是处理复杂性的一个原则,一种方法。
关注点分离是面向方面的程序设计的核心概念。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中不再含有针对特定领域问题代码的调用(将针对特定领域问题代码抽象化成较少的程式码,例如将代码封装成function或是class),业务逻辑同特定领域问题的关系通过侧面来封装、维护,这样原本分散在在整个应用程序中的变动就可以很好的管理起来

关注点分离(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种分离层次的概念,每一种概念都可以任意的与其他概念组合在一起,从而产生更多的变化。

在实际的开发过程中,没有东西是一成不变的,而层次和架构也应该是在开发的过程里面不断完善和重构。

初级程序员最烦的是需求或业务的修改,一些我们觉得奇奇怪怪的修改导致大家不断的修改代码,心里很烦,在心里也默默的把产品经理被翻过来倒过去骂了千百遍。

但是,在实际的工作中你会发现,软件开发就是这样,没有什么是不变的。

如果一定要找出一个不变的点,我想那应该是:

唯一不变的,就是变化。

关于不断的修改与重构,也可以参考《重构-改善既有代码的设计》,记得封面上写的是:

软件开发的不朽经典

生动阐述重构原理和具体做法

普通程序员进阶到编程高手必须修炼的秘笈