见山只是山 见水只是水——提升对继承的认识

温 昱

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

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

一、从一则禅师语录说起

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

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

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

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

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

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

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

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

二、从OOD层面认识继承

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

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

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

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

1、相关理论

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

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

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

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

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

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

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

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

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

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

笔者曾有一个项目,该系统需要实时地将本系统的数据变化,通知远端的另一个系统。相关设计如下图所示。在问题领域层,仅包含了一个抽象接口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期

以及来自www.objectmentor.com的多篇文章

作者简介:

温昱,架构设计师,资深咨询顾问,松耦合空间(http://lcspace.nease.net)创办人。擅长面向对象、架构和框架设计,对设计模式、UML和软件工程有深入研究。可以通过wenyu@china.com和作者联系。

见山只是山 见水只是水——提升对继承的认识相关推荐

  1. 【转载】见山是山 见水是水

    从前有位叫惟信的老禅师.有一次他上堂时说出了自己悟道的体会:"老僧三十年前未参禅时,见山是山,见水是水.及至后来,亲见知识,有个人处,见山不是山,见水不是水.而今得个休歇处,依前见山只是山, ...

  2. 见山还是山,见水还是水,见程序还是程序

    禅宗有个著名的故事,宋代吉州青原惟信禅师曾说:"老僧三十年前未参禅时,见山是山,见水是水.及至后来亲见知识,有个入处,见山不是山,见水不是水.而今得个休歇处,依前见山只是山,见水只是水.大众 ...

  3. 见山只是山,见水只是水——提升对继承的认识

    见山只是山 见水只是水--提升对继承的认识 作者:温 昱 本文发布于<CSDN开发高手> 封装.继承.多态是OO 的三大特性,由此可见继承思想的重要性.但是,不少人对继承的理解过多地局限在 ...

  4. 见山是山,见山不是山,见山只是山

    "老僧三十年前未参禅时,见山是山,见水是水.及至后来,亲见知识,有个入处.见山不是山,见水不是水.而今得个休歇处,依前见山只是山,见水只是水.大众,这三般见解,是同是别?有人缁素得出,许汝亲 ...

  5. 山还是山 水还是水

    古人云:空山新雨后,天气晚来秋. 山还是山,水还是水,山未变,水未迁,只是尔心变,所以看山不是山,观水亦非水. 山的豪迈,装点了水的娇柔;山的粗犷,凝莹了水的婉约;山的雄浑,突显了水的多姿. 不必去在 ...

  6. 【文章】鱼那么信任水, 水却煮了鱼!

    鱼那么信任水, 水却煮了鱼. 叶子那么信任风, 风却吹落了叶. 人心的冷暖, 总是一直变幻. 熟悉的陌生了, 陌生的走远了. 人与人之间, 全靠一颗心, 情与情之间, 全凭一寸真. 落叶知秋,落难知友 ...

  7. 鱼那么信任水,水却煮了鱼

    烟那么信任火, 火却点燃了它, 烧成灰烬. 鱼那么信任水, 水却煮了它, 鱼没了命. 这世间最难揣测的就是人心, 表面热情的,心里冷漠, 看似友善的,背后插刀. 熟悉的,也会渐渐陌生, 相爱的,也会越 ...

  8. 每日一言:山还是山,你还是你

    山还是山,你还是你 The mountain is still itself and you are still yourself 不变的心给了固执的自己 I left the same love o ...

  9. 鱼那么信任水, 水却煮了鱼!

    鱼那么信任水, 水却煮了鱼. 叶子那么信任风, 风却吹落了叶. 人心的冷暖, 总是一直变幻. 熟悉的陌生了, 陌生的走远了. 人与人之间, 全靠一颗心, 情与情之间, 全凭一寸真. 落叶知秋,落难知友 ...

  10. 【操作系统】某寺庙,住着一个老和尚和若干小和尚,有一个水缸,由小和尚提水入缸供老和尚饮用。水缸可以容纳10桶水,水取自同一口井中,由于水井口窄,每次只能容纳一个水桶取水,水桶总数为3个。每次往水缸中倒

    题目 某寺庙,住着一个老和尚和若干小和尚,有一个水缸,由小和尚提水入缸供老和尚饮用.水缸可以容纳10桶水,水取自同一口井中,由于水井口窄,每次只能容纳一个水桶取水,水桶总数为3个.每次往水缸中倒水与从 ...

最新文章

  1. Spring 2企业应用开发
  2. python3 获取当前路径_python3获取当前目录(转)
  3. AngularJS中关于ng-class和*ngIf指令
  4. 华为达芬奇架构到底好在哪里?
  5. CSDN《IT人才成长路线图》重磅开源!60+ 专家,13 个技术领域,绘出 35 张图谱...
  6. Ruby之旅(16) 异常处理 rescue与ensure
  7. PHP防止表单重复提交的解决方法
  8. Flex游戏篇——游戏开发概述
  9. php poedit怎么debug,Cakephp的国际化和poEdit的使用
  10. pytorch模型预测
  11. 《软件测试过程改进》读书笔记
  12. 微信小店二次开发_怎么吧微信小店装修的更好看
  13. 2020-08《信息资源管理 02378》真卷(独家文字版),圈定章节考点+统计真题分布
  14. python 内置函数 和 匿名函数
  15. Excel的图表:组成元素、图表类型与用途、图表可视化大全
  16. MFC之图像绘制---高速绘图控件(High-speed Charting Control)应用(一)
  17. JavaBean 技术与 JSP 开发模型练习题
  18. 游戏开发者的成长之路:C++经典项目控制台贪吃蛇(在GitHub热门项目上增添功能及修改bug)
  19. 计算机类考研文献翻译有哪些,考研专业文献翻译如何准备
  20. P2P流媒体直播的疑问

热门文章

  1. 微信建群怎么建?2个方法,快速学会!
  2. dfuse for EOSIO v0.1.0-beta4 版本更新说明
  3. 基于Python的招聘网站招聘信息分析
  4. PL330 DMAC笔记(1) - 简介
  5. 科研难做,何不使用Nvivo?
  6. 印度文明的继承和交溶
  7. rest_framework--过滤器filters--搜索
  8. Linux系统的安装与配置
  9. 解决“你没有权限访问,请与网络管理员联系”
  10. Pintos-斯坦福大学操作系统Project详解-Project1