# 12.4 保变原则——与魔鬼打交道的艺术
保变(Protected Variation,PV)是指受保护的变化。
内容是:找出预计的变化点和不稳定点,分配其职责以便用稳定的接口和包装。
该原则有助于构建低耦合高内聚系统。
保变原则与单职原则一样,中心都是职责和变化。不过,保变原则着眼点更倾向变化,而单职原则的着眼点更倾向职责。
前者侧重解决耦合问题,后者侧重解决内聚问题。
封装是实现保变的一种技巧和手段
保变涵盖的范围很广,可以是任何软件实体,包括模块、组件、子系统乃至系统。
封装是类级别的保变,或者说是数据抽象层的保变。同理,子类型多态是接口级别的保变,或者说是多态抽象层的保变。
软件最大的变数是客户的需求
可维护性直接关乎软件的生命力,软件的可重用性主要是为了保证可维护性。
所以可维护性是软件的设计质量最重要的标准。
事实上,越是接近客户需求的类,越容易发生变化,稳定度就越低,有些修改时非常正常的。
因此一味追求高可重用性,不仅是不必要的,更是不现实的。只要这种连带修改涉及面小,维护方便,即使可重用性低些有有何妨
其实,当代码朝着高可维护性方向前进时,高可重用性也会如影随形。而一段容易维护的代码,又怎么会不够灵活、难以扩展,或者难以理解呢?
对于维护者来说,变化是魔鬼。代码维护便是与魔鬼打交道。
魔鬼代表变化,铁笼代表稳定的封装,孔代表对外的接口。这驯魔大法其实就是保变原则。
保变原则是一个增加软件可维护性的通用而有效的方法。
保变原则与间接原则异曲同工
间接原则一个典型应用是:为解除两个模块之间的耦合,专门引入一个中间模块。
耦合并不总是有害的,只有当其中潜藏的变化明显不利于将来的维护时,才有消除的必要。
这个中间模块的作用,就是将此变化加以抽取和封装,并承担起中介的职责。
保变原则与开闭原则也神似
只是,保变原则提倡变中求稳,更侧重可维护性;开闭原则提倡稳中求变,更侧重可重用性。
保变原则还暗含了迪米特法则(LoD)又称最少知识原则(Least Knowledge Principle, LKP),即不要与陌生人交谈(Don't talk to Strangers)。
具体到应用到方法调用上,它要求一个对象的方法只能调用以下对象:改对象本身,即this或self;该方法的参数;该方法内部创建的对象;该对象直接组成对象,包括其属性及集合性属性中的元素。
P384 图12-13 不满足LoD的实例
如 obj.method1().method2().method3()之类的接龙式调用,大多都违背了迪米特法则。
迪米特法则教导我们:可以信任朋友,但不可以信任朋友的朋友。
换句话说,朋友关系没有传递性。
在常见的封闭型多层架构中,上层只依赖相邻的下层,不能越层请求服务,也是同样的道理。
每个模块都有清晰的逻辑边界和物理边界,但它的交际圈却没有。
为了弥补这一缺憾,迪米特法则对模块的交际圈做出了明确的界定。如此一来,发生在模块上的变化受到双层保护:
一层是模块自身的保护机制
一层是模块的交际圈的保护机制
GRASP 通用职责分配原则
(General Responsibility Assignment Software Patterns/Principle) 通用职责分配原则
它给出了如何合理的分配职责的方案。
表12-1 GRASP
信息专家(Information Expert)
职责分配的基本原则是什么?
把职责分配给信息专家——拥有完成该职责所需信息的类。
控制器(Controllor)
谁负责处理系统事件?
把处理系统事件的职责分配给代表整体系统或事件发生的用例场景(use case scenario)的类
创建者(Creator)
谁负责创建对象?
把创建类A的实例的职责分配给类B,如果以下之一成立:
B包含A;B聚合A;B记录A;B密切地使用A;B拥有A的初始化数据。
低耦合(Low Coupling)
如何支持低依赖、高重用?
分配职责已保持低耦合度
高内聚(High Cohesion)
如果驾驭复杂性?
分配职责以保持高内聚度
多态(Polymorphism)
当行为因类型而变化时,谁负责?
当相关的备选方案或行为因为类型而变化,利用多态机制把职责分配给行为变化的类型
纯虚构(Pure Fabrication)
当你不想违背低耦合高内聚原则却又找不到合适的负责者时,该怎么办?
将一组高内聚的职责类集合分配给一个虚构的“行为”类(即不代表问题领域的概念),以支持低耦合高内聚。
间接(Indirection)
如何分配职责以避免直接耦合?
把职责分配给一个中介对象已避免两个组件或服务的直接耦合。
保变(Protected Variations)
如何好分配职责对象、子系统或系统,以使这些元素的变化不会影响到其它元素?
找出预计的变化点或不稳定点,分配其职责以便用稳定的接口来包装。
信息专家原则和纯虚构原则是关键,
创建者原则和控制器原则可看作他们的应用。
信息专家原则可归结为:知情者为负责者,即谁拥有完成职责所需的全部信息,谁就该负责。
知情者太多是设计可能有缺陷的一个征兆,如果确实无法避免,就选育职责关联度最大的那位知情者。
知情者具备了承担职责的基本能力,与相关信息也有一定的内置联系,让它来负责即是可能的,也是合理的。
以信息专家作为职责分配的基本原则,可令系统的整体功能比较均匀地分散于各个类中。每当变化来临时,由于职责分工明确而公平,相关承担者很容易被识别且范围有限。同时职责分量不会太大,因而系统较易维护。
信息专家原则多少还是增加了系统的耦合,当增加的耦合明显阻碍可维护性或可重用性时,就需要求助纯虚构原则了。
所谓虚构,是指这种类有别于通常的类,并不代表问题领域中的概念,而是彻头彻尾的虚拟构想;所谓纯,是指虚构类的职责单纯是魏了支持低耦高聚,因而它的设计会显得很干净或纯净。
比如,创建者原则建议:创建一个类的实例的职责分配给那些包含、聚合、记录或密切使用该类的类,或者拥有该类初始化数据的类。
这个原则非常自然,算得上信息专家原则的一个推论,但同时也在两个类之间建立了强耦合。
为此可以专门设计一个工厂类,接管创建对象的职责——这便是著名的工厂模式。
该工厂类无疑符合纯虚构原则,因为工厂原本是不存在的。
再如,控制器原则建议:把处理系统事件的职责分配给两种类,一种是代表整体系统的类,符合信息专家原则;一种是代表事件发生的用例场景,符合纯虚构原则。
还有一个典型范例是领域驱动设计(domain-driven design)中的服务。
它是不同于实体和值对象的一种特殊对象,不含任何状态,唯一的职责是把模型中相关的实体或值对象组织起来,为客户提供一些列的相关服务。
每一种服务都是一种行为ie而非实体、动词而非名词的抽象,也是纯虚构原则的体现。
SOLID 类设计原则
SRP 一个类应当只由一个变更的理由
OCP 软件实体对扩展开放,对修改封闭
LSP 子类型必须能替换超类型
ISP 不应强迫客户依赖那些它们不用的方法
DIP 抽象不应依赖细节,细节应当依赖对象
SOLID 是牢固的意思,GRASP是掌握的意思,都是OOD的重要原则
包(Package)级设计原则
发布重用等价原则 REP(Release-Reuse Equivalency Principle) 重用的粒度是发布的粒度
共同闭包原则 CCP (Common Closure Principle) 同一个包中的类应对同类变化封闭
共同重用原则 CRP (Common Reuse Principle) 同一个包中的类一起被重用
无环依赖原则 ADP (Acyclic Dependencies Principle) 在包的依赖图中不允许有环的存在
稳定依赖原则 SDP (Stable Dependencied Principle) 朝着稳定的方向依赖
稳定抽象原则 SAP (Stable Abstractions Principle) 一个包的抽象度应与其稳定度相当
前3个是关于包的内聚原则,以解决包的颗粒度(granularity)问题,后3个是关于包的耦合原则,以解决包的稳定度(stability)问题。
设计原则中的关键词和彼此之间的关系
P382 图12-15