UML软件工程组织

见山只是山 见水只是水——提升对继承的认识
作者:温 昱 本文发布于《CSDN开发高手》

 

封装、继承、多态是OO的三大特性,由此可见继承思想的重要性。但是,不少人对继承的理解过多地局限在OOP层面,从而限制了继承思想在OOD层面的巨大作用。笔者认为,软件工程师应该不断提升对OO思想的认识层面,加强实际开发能力。

本文站在OOD的角度,将继承看成实现OOD的强大手段,通过具体例子,说明针对接口编程(Program To An Interface)、混入类(Mix In Class)、基于角色的设计(Rolebased Design)这三个与继承紧密相关的著名OOD技巧。

 

 一、从一则禅师语录说起

 

《五灯会元》卷十七中,有一则青原惟信禅师的语录:“老僧三十年前未参禅时,见山是山,见水是水。及至后来亲见知识,有个入处,见山不是山,见水不是水。而今得个休歇处,依前见山只是山,见水只是水。”

禅师高论,颇具哲理,讲的是悟道的过程。其实,领悟OOD之道的过程又何尝不是如此呢?

 

1、见继承是继承——程序员境界

初学OOP的人,大多处在“见继承是继承”的层面,最关心的是类的语法、类的成员变量、类的成员函数等这些实现层的东西。这是程序员境界。

 

2、见继承不是继承——成长境界

开始研习OOD之时,又往往跳到另一个极端,只关心设计,而无心(也可能是无力)关心实现,处在所谓“见继承不是继承”的层面。在这个阶段的人,脑中的兴奋点是“设计”,是职责分配、接口设计、可重用性、可扩展性、耦合度、聚合度等这些设计层的概念。这是成长境界。

 

3、见继承只是继承——设计师境界

学通OOD之后,会达到“见继承只是继承”的层面。一个“只”字,体现了继承背后的“设计理念”才是该境界的要害。但是,这个阶段和第二阶段不同,第二阶段是一味的否定,而本阶段是否定之否定,把OOP层面的继承机制看成用来实现特定OOD的手段加以利用。这是设计师境界。

  

二、从OOD层面认识继承

 

OOP层面,除了类、成员变量、成员函数这些最基本的概念,最重要的就是代码重用和名字空间的可见性了。而OOD层面,最基本的概念是类、职责、状态、角色这些更抽象一级的概念,及其相关的耦合度、聚合度、可重用性、可扩展性、可维护性等。可见,虽然OOD最终要依赖OOP作为实现手段,但显然OODOOP并非在同一抽象级上,有不同的概念体系和思维方式。

再说继承。单纯从OOP层面看,继承是一个通过复用父类功能而扩展应用功能的基本机制,它允许你根据旧的类快速定义新的类;还有些人用继承仅为了获取名字空间的可访问性。但是,从OOD层面看,继承可以演变出 “Is-A”、“Plays Role Of”等抽象的设计概念。因此,担任设计师角色的人如果自己还限制在OOP的层面,“设计乏术”的局面是不可避免的。总之,提升对继承的认识,对活用接口继承和实现继承这两种继承机制来实现OOD意图非常重要。

与继承相关的OOD技巧有很多,本文仅讨论比针对接口编程、混入类、基于角色的设计这三种技巧,下图展示了它们和继承的关系。

  

三、针对接口编程——隔离变化

 

1、相关理论

耦合是依赖的同义词,被定义为“两个元素之间的一种关系,其中一个元素变化,导致另一个元素变化”。抽象耦合被定义为“若类A维护一个指向抽象类B的引用,则称类A抽象耦合于B”。

依赖性倒置原则(Dependency Inversion Principle)形式化了抽象耦合的概念,明确表述了应该“依赖于抽象类,不要依赖于具体类”。

针对接口编程遵守上述原则,从而在很大程度上阻止了变化波及范围的扩大,有效地隔离了变化,有助于增强系统的可重用性和可扩展性。

 

2、针对接口编程举例——用于体系结构设计

根据经典的CoadOOD理论,一个项目通常包含四个层:用户界面层、问题领域层、数据管理层、系统交互层,如下图所示。

将体系结构划分为层的一个很大好处是,这些层形成了开发小组的自然分界——每层的开发人员所需要的技巧是不同的。用户界面层的开发小组需要了解将使用的用户界面工具包;数据管理层的开发小组需要熟悉相关的数据库、持久工具或者使用的文件系统;系统交互层的开发小组需要了解通讯协议和用到的中间件产品;问题领域层的开发小组不需要了解这些知识,他们需要最深的领域知识,以及用到的相关分布对象或组件技术。

但是,要真正使得各个开发小组最大限度地独立开发,还需要一个稳定的体系结构设计做保证才行,其设计的核心思想是:问题领域层“不依赖于”其他任何层,而其他任何层“只依赖于”问题领域层。如下图所示。

该体系结构设计的实现,极为重要的一点,就是要使用针对接口编程的技巧。以系统交互层对问题领域层的单向依赖为例:

Ø         如果系统交互层要调用问题领域层的操作,直接调用即可。

Ø         如果问题领域层要调用系统交互层的操作,需要由问题领域小组定义一个通用的抽象接口,通过针对接口编程调用这个抽象接口;而系统交互小组通过接口继承机制,定义抽象接口的子类,该子类完成抽象接口的具体实现。

笔者曾有一个项目,该系统需要实时地将本系统的数据变化,通知远端的另一个系统。相关设计如下图所示。在问题领域层,仅包含了一个抽象接口CChangeReporter,而并不关心CChangeReporter的具体实现。系统交互层拥有选择具体实现方法的自由,比如CSoapChangeReporter是用SOAP通讯协议实现的CChangeReporter CTcpChangeReporter是用TCP协议实现的CChangeReporter。而且假设由于技术的或商业的原因,将来需要同时支持多种通讯协议,也比较容易。

 

3、针对接口编程举例——用于类设计

笔者曾在《运用设计模式设计MIME编码类》一文中,详述了如何使用策略模式来设计一个可重用、易扩充的MIME类层次,其中抽象接口类CMimeAlgo起到了至关重要的作用,现简述如下。

用户通过CMimeString使用MIME编码的功能,CMimeString允许用户在运行过程中动态配置MIME编码的具体算法;具体MIME编码算法由CMimeAlgo类层次提供,具体的CMimeAlgo子类的实例化是由CMimeString根据用户的配置动态完成的;要增加新的MIME编码算法,只需实现新的CMimeAlgo子类,并简单扩充CMimeString的动态实例化代码即可。如下图所示。

 

四、混入类——更好的重用性

 

1、相关理论

混入类被定义为“一种被设计为通过继承与其他类结合的类”,它给其他类提供可选择的接口或功能。

从实现上讲,混入类要求多继承;混入类通常是抽象类,不能实例化。

混入类的作用在于:它不仅可以提高功能的重用性,减小代码冗余;而且还可以使相关的“行为”集中在一个类中,而不是分布到多个类中,避免了所谓的“代码分散”和“代码交织”问题,提高了可维护性。

 

2、混入类举例

来看一个具体项目。在一个信用卡客户服务系统项目中,要求能够以多种方式发送多种信息给用户,并能够适应未来业务的发展变化。

当前系统需要支持的发送方式:

Ø         打印(并邮寄)

Ø         Email

Ø         传真

可预见的未来要支持的发送方式:

Ø         手机短信

Ø         PDA消息

当前系统需要支持的待发送信息:

Ø         信用卡对账单

Ø         信用卡透支催收单

可预见的未来要支持的待发送信息:

Ø         信用卡新业务宣传单

Ø         信用卡促销活动宣传单

下面是一些设计考虑。一种发送方式要支持多种待发送信息,我们希望发送功能有很好的可重用性;为了方便未来加入对新的发送方式和发送信息的支持,设计必须具有良好的可扩展性。相关设计如下图所示。其中采用了混入类的OOD技巧,用一个CSendableDoc作为混入类,支持发送功能的重用;CSendalbeDoc还采用了策略模式支持发送方式的扩充。

 

 

五、基于角色的设计——使用角色组装协作

 

1、相关理论

协作被定义为“多个对象为了完成某种目标而进行的交互”。角色被定义为“特定协作中的对象的抽象”,它“仅定义了对象特征的一个对某协作有意义的子集”。协作和角色的概念和现实世界很接近,比如下图中,Jane教授扮演三个角色——母亲、妻子、教授。

接口分离原则(Interface Separation Principle)信奉“多个专用接口优于一个单一的通用接口”的思想,因为“任何接口都应当具有高内聚性”,以便“保证实现该接口的类的实例对象可以只呈现为单一的角色”。

基于角色的设计的意义在于:我们很容易通过已有角色的组合来构造新的协作,以完成新的功能。而且,从UML类图可以很自然地导出基于角色的设计方案,例如:

 

从上面的类图很自然地导出下面的设计:

2、基于角色的设计举例

比如,待开发的一个系统,其后台数据源可能是关系数据库、一般的文件、还可能是另一个私有数据库。既然接口可以隔离变化,我们可以定义一个单一的接口,为所有的数据客户类提供服务。如下图所示。

但是,上面的设计违背了基于角色的设计思想,根本不能保证“实现该接口的类的实例对象可以只呈现为单一的角色”,这会带来一些问题。比如,有一个数据客户类,不需要插入、更新等功能,而仅仅需要对数据进行读操作,这时显然一个提供“读”服务的“角色”是最合理的设计,但CRowSetManager却是如此之“宽”的一个接口。最终,我们可以这样来改进设计,如下图所示。

 

参考文献:

《设计模式》 Erich Gamma等著 李英军等译

《重构——改善既有代码的设计(影印版)》 Martin Fowler

UML面向对象设计基础》 Meilir Page-Jones著 包晓露等译

Java设计:对象、UML和过程》 Kirk Knoernschild著罗英伟 汪小林译

《特征驱动开发方法原理与实践》Stephen R. Palmer, John M. Felsing著熊焕宇等译

Object-oriented programming: Role-based design W.McUmber 来自网上的幻灯片

Role = Interface: A Merger of Concepts Friedrich Steimann 来自JOOP

《运用设计模式设计MIME编码类》 温昱 CSDN开发高手》第1


版权所有:UML软件工程组织