耦合的起源和控制类存在的理由

类别:软件工程 点击:0 评论:0 推荐:


古老的面向对象原则教导我们,要把状态和行为封装到一起,一个类不应当修改另外一个类的状态。控制类的概念似乎违反了这个原则,尽管多数设计中控制类不会直接修改封装在另外一些类中的数据,但控制类中的代码的确是行为的一部分,而间接修改其他类中的状态。这是否就可以得出结论,包含控制类的设计是不好的设计,或者采用控制类会严重破坏软件的结构,把这种破坏看作是一种成本的话,控制类的成本非常高,只有在少数情况不使用控制类成本更高的情况下,才应当使用控制类?

但实际情况是,在绝大多数好的设计中都会存在大量的控制类,控制类的使用应该是很普遍的,而非特例,刻意排斥控制类的概念只能导致低劣的设计。控制类的引入成本,并非想象中的那么大,分析控制类带来的效益,可以让人对面向对象的本质有更深刻的认识。

如果我们看看GOF的设计模式,比较适用于搭建大的结构(或者说是软件架构)的模式,只有Command、Facade和Mediator,只有这些模式在多数情况下能完美的把整个软件划分为最大的几个顶层模块。用控制类的概念的角度观察这三个模式,很容易发现这三个模式的主要内容,都是如何把控制较小对象的控制逻辑丰装载专门的类中,这些类,即Command类、Facade类和Mediator类,除了控制其他对象之外,没有承担别的职责,都是很单纯的控制类。也就是说,多数情况下,在软件的架构设计中必须把控制逻辑专门抽取出来封装成为控制类,是最好的选择。
------------------------------------------
Command、Facade和Mediator模式是如何使用控制类的模式
------------------------------------------

按照GOF的说法,这三个模式的主要目的,是要削弱模块之间耦合,实际上这些模式之所以能够解耦,正是因为它们使用了控制类的概念。暂且不论控制类是否存在其他的好处,控制类的一个重要的作用是消除模块之间的耦合,更重要的是只有控制类才能圆满消除模块之间的耦合,而同时还能够让各个模块之间的协作关系清晰可见。而其他一些不封装控制逻辑的模式,例如Observer模式和职责链,虽然可以削弱模块之间的耦合,但是却让模块之间的互作用变得让人迷惑。

----------------------------------------
紧密耦合的产生根源,是我们把整个软件划分为多个模块。
----------------------------------------
当然谁都不会把整个软件设计为一个类,一个对象,这样面向对象的设计就会退化为面向过程的设计。我们希望把整个系统的数据或者状态,划分为非常多的部分(每个部分是一个对象)之后,能够比较容易操纵一些。但这样一来,多个对象之间就不能保持独立,因为需求中要求的行为,是对软件管理的状态的整体的改变,而不是对单一对象的状态的改变,也就是需求中每一个功能都会要求同时改变多个对象的状态,而不是只改变一个对象的状态。这样,必然某些对象需要调用其他对象,这就是控制逻辑,控制逻辑是不可避免的,因为系统中有多个对象需要协调的同时改变它们的状态。

如果设计中的所有类的职责,都是管理自己的状态,那结果必然是这些类互相调用,无法划分出层次,形成紧密的耦合。很容易发现,这样的设计中,不可能有任何类可以在其他程序中重用,不可能有任何类的内部修改,可以和其他类无关。

要解除模块化分导致的紧密耦合,必须把控制逻辑和状态管理逻辑区分开来,分别封装到不同的类中,这样至少我们会得到一些不依赖软件其他部分的类,这些类是完全被动的,可以拿到另外一个程序中重用而不产生任何问题,如果这些孤立的类中有bug,我们也会很有信心的认为对这些bug的修改,绝对不会影响到其它部分。孤立的类是无法工作的,要让被动的类工作,必须有控制类,控制类和被动类的依赖关系是单向的,只有控制类依赖被动类,被动类不依赖控制类,也不依赖其他被动类。

控制逻辑是那些保证能够按照需求,协调的改变多个对象的逻辑,他们保证多个对象能够正确的同时改变自己的状态,控制逻辑是主动逻辑,是唯一导致对象之间的依赖的因素,从松耦合的角度看控制逻辑是设计中必须的坏东西。而状态管理逻辑,则是改变某个封装边界内部的状态,维持边界内部的状态的正确性,单纯的状态管理逻辑,可以完全孤立在封装边界内实现。

控制类之所以能够解除耦合,是因为他把系统中的所有必要主动依赖,全部抽取出来集中到自己的内部,从而使其他类不再有依赖关系,并使其正确性和封装边界外部无关。
--------------------------------------------
控制类抽取并且封装了系统中所有依赖关系,是设计中的垃圾收集器
--------------------------------------------

这样,控制逻辑封装不完备的后果也是很明显的,如果一个系统中的控制类没有能够封装所有的依赖,这些剩余依赖必然会泄漏到其他管理状态的类中,导致它们互相耦合,而他们本来可以完全互不相干的。

为什么不能让管理状态的类,去承担协调不同对象行为的控制逻辑?
------------------------------------------
因为这样会降低对象的内聚度,加强耦合
------------------------------------------

因此不能把所有修改某个状态的逻辑,全部封装到保存这个状态的类中,而是把修改状态的逻辑分为控制逻辑和状态管理逻辑,分别封装到不同的类中。还剩下一个问题否则无法操作,如何区分控制逻辑和状态管理逻辑,二者都直接或者间接修改了状态,这个问题是被动类的边界的问题,如果所有的类被划分为被动类和控制类,则必然需要在控制类和被动类之中分配职责,某些职责可以分配给控制类,也可以分配给被动类,只要不把主动逻辑分配给被动类,就不会导致紧密的耦合。因此可以得到以下被动类职责最大化原则:
-------------------------------------------------
假如某种职责假如被动类,不会导致被动类调用同一层次的其他被动类,则把这个逻辑分配给被动类
-------------------------------------------------

被动类可以是架构中的顶层模块,也可以是实体类。实体类是最底层的模块,比较容易识别。如果被动类是顶层或这中间层次的模块,则被动类的职责划分,存在层次划分的问题,因为这些模块在上一级层次看起来是被动类,但是在下一级层次看起来就是控制类,实际上是对控制逻辑的分级封装。

这样,纯粹的状态管理逻辑,全部封装在实体类中,实体类之外的类所做的事情,通常称为“业务逻辑”,很容易发现:
---------------------------------------------
业务逻辑就是所有的控制逻辑,他们应该被封装到各个层次的控制类中
---------------------------------------------
进一步的,我们可以得到“业务逻辑”的更根本的性质:
---------------------------------------------
业务逻辑是那些不能在另外一个程序中重用的逻辑
---------------------------------------------
或许上面的说法过于绝对,但是业务逻辑涉及到的概念的普遍性,远远小于实体类对应概念的普遍性,实体类的可重用范围,远远大于控制类的可重用范围。因此我们也可以使用可重用范围,来划分控制类的层次。一个层次的控制类的可重用范围,等于这些这个层次中的类中重用范围最小的类的可重用范围。
--------------------------------------------
一个层次中的各个模块的可重用范围应该相当,而不同层次的类的可重用范围应该有较大的差距
--------------------------------------------
因此,可重用范围越大,可重用的内容就越少,划分层次的目的是为了在更大范围内重用更多的内容。

 

本文地址:http://com.8s8s.com/it/it33195.htm