zoukankan      html  css  js  c++  java
  • 艾伟_转载:闲说继承 狼人:

    继承已经是一个古老的话题了,不过最近又在一些地方看到有人讨论它,加上自己也有一些想法,因此形成了这篇文章。

    继承好不好?

    经典的OO理论说:继承是面向对象的三大基石之一。
    现代的OO理论说:组合优于继承。

    这两种说法显然是彼此冲突的。如果组合优于继承的话,那么为什么组合没有取代继承成为OO的基石呢?哪一种说法更有道理?
    对这个问题,简单的说哪个比哪个更好其实是没有多大意义的。我们应当从技术发展的历史角度去看,这两种说法各自是在什么时期产生的,它们形成的背景是什么,才能对此问题有一个更加深刻的理解。

    面向对象的思想形成与上个世纪70年代,但真正在软件开发阵营中流行开则是在80年代末和90年代初的时间。巧合的是,这一时间也正是以Windows 3.x为代表的图形操作系统兴起的时代。于是面向对象当时所面临的主要问题就是:如何以OO的理论封装图形界面的开发?很多重要的早期OO思想都是在这个时期形成的,包括对于继承的使用。

    让我们考虑一下图形界面的特点。很容易发现:这个领域确实非常适合使用继承,因为图形对象天生就存在着is-a关系。比如,所有图像对象都是Window,所有对话框都是Dialog,所有按钮都是Button,等等。所以我们可以看到的结果就是:所有的图形界面框架都大量使用了继承,而且继承的层次通常都非常深。例如,下图是WPF中最主要的界面类——Window的继承关系,它的继承层次深达9层!

    所有图形框架在继承方面几乎无一例外。Java Swing对图形框架由于较多使用MVC,因此继承的深度要浅一些,但是主要的JFrame类继承深度也达到了6层:

     

    至此我们应该理解,为什么早期OO理论要将继承作为面向对象的基石了。因为当时软件开发的领域还比较狭窄,所以很多开发者根据自己在图形领域的开发经验认定:继承是OO必不可少的重要基础,并且应当尽可能的使用。


    随着历史的发展,软件开发逐渐进入了两层和三层时代。程序员发现,原来在桌面应用中得心应手的继承突然之间不那么好用了。为什么呢?
    原因之一:两层和三层开发的主要工作之一是对实体建模。而现实中的实体大多数是相对独立的,它们之间的关系更多的表现为实体之间的关联,而不是从属关系;
    原因之二,很重要的现实问题:多层开发的主要物质基础之一——关系数据库,无法很自然的描述继承关系。事实上这也是ORM出现的重要理由之一。但即使是现在最好的ORM工具,要在数据库中描述继承关系仍然非常复杂。这迫使程序员在相当程度上放弃了继承;
    原因之三:分层的开发方式逐渐流行开来,而继承造成的类属关系耦合非常不利于分层。

    出于这些考虑,现代的OO理论为什么更加推荐组合而非继承,应该就容易理解了。
    那么现代OO理论是不是对于继承的看法就完美了呢?我认为也不是。事实上我认为,现代OO理论存在着忽视继承的问题,很多理论书籍只是简单的告诉我们优先使用组合,而根本就不告诉我们在什么时候应当合理使用继承,什么时候不应当使用。这是从早期OO的过度使用继承跳到了另一个极端,也是不可取的。

    接下类我要讲讲对于继承的几个常见的错误观念。

    1. “组合优于继承。”
    就一般的意义上说,这个讲法是没错的,但问题在于实在太简略了。它并没有告诉我们什么情况下组合优于继承。一个很自然的问题就是,如果组合在任何情况下都优于继承的话,那继承还有存在的必要吗?

    有些情况下继承确实比组合要好。再回到图形界面的例子,Button继承于Window(这是早期MFC的叫法;在WinForm/WPF的分类中,Button继承于Control,Window通常用来定义顶层窗口),这是没有问题的,如果一定要用组合来实现Button的话,反而会导致不必要的复杂性。之所以这种情况下继承更好,根本原因是这里存在着确定的is-a关系(Button is a Window)。所以我们可以得出这样一个结论:如果语义上存在着明确的is-a关系,则考虑使用继承;如果没有,使用组合。

    需要说明的是,这个结论其实也并不是完整的,原因我在后面还会继续讲到。

    2. “继承的目的是为了复用。”
    这个说法根本是错误的,但就是这个错误说法的流行程度简直让人吃惊。继承并不是为了复用,继承的根本目的是为了对现实世界进行更好的建模,容易复用只是优秀模型的一个必然结果而已。我们不能倒果为因,特别是,我们不应该为了复用的目的而去继承。

    举一个现实的例子。汽车可以复用轮子的一些特性(比如可以Run和Stop),那么我们应当让汽车从轮子继承吗?我看到真的有一些人就是这么建模的。但是从逻辑上想一想就知道,这是非常不合理的,汽车并不是轮子。我们建立了一个错误的模型,这会让我们在以后付出代价——比如说,要让汽车能够换轮子怎么办?只好傻眼了。

    再次强调:继承的目的不是复用,不应当为了能够复用而使用继承。你应当尽力去建立一个逻辑合理的模型,不应该仅仅为了方便而扭曲这个模型。

    3. 只要存在is-a关系就应当使用继承
    在第一点我说过:如果语义上存在着明确的is-a关系,则考虑使用继承;如果没有,使用组合。我还补充说这个结论并不完整,这里就会说明原因。

    我们还是从一个例子说起。下面是许多OO书籍都会提到的一个经典例子:

     

    在这个模型中,Sales和Manager都是Employee,但是它们计算薪水的方法是不同的。不同的记薪方法可以通过重载getSalary()方法来实现。

    这么经典的例子有没有问题呢?有!我们可以这样想,“如果雇员被提升为经理,会怎么样?”


    问题来了。在OO的世界中,对象所属的类型是这个对象的本质属性,任何对象在生命期间无法改变自己所属的类别。但是现实中对象的身份很多时候是可以改变的。我们从这里可以发现继承的一个重大问题:一旦对象的身份发生改变,那么继承层次就完全崩溃了。

    那么图形界面中为什么可以使用继承呢?因为图形界面领域的对象身份是相当稳定的。Button就是Button,它不会突然变成一个顶层窗口。所以这里使用继承不会发生任何问题。但是对于类型可变的场合,继承是不适合的。

    从建模的角度,我们也可以这样理解:是Sales还是Manager,并不是一个人的本质属性,它是可变的。一个人的本质属性只有他自身(姓名、性别事实上都是可变的)。我们不能够把非本质属性应用到继承层次上面。

    所以上面的结论应该这样表述才算完整:如果语义上存在着明确的is-a关系,并且这种关系是稳定的、不变的,则考虑使用继承;如果没有is-a关系,或者这种关系是可变的,使用组合。

    我们可以使用策略模式来将上面的例子重构为使用组合,如下图所示:

    从上述结论我们可以看到,继承的使用的确是受到很多限制,在很多情况下也确实是组合优于继承。但是不分场合、不论条件的认为组合一定比继承好,也是过于教条主义的表现。合理的做法只有一个:具体问题具体分析。

  • 相关阅读:
    jMeter 里 CSV Data Set Config Sharing Mode 的含义详解
    如何使用 jMeter Parallel Controller
    使用 Chrome 开发者工具 coverage 功能分析 web 应用的渲染阻止资源的执行分布情况
    使用 Chrome 开发者工具的 lighthouse 功能分析 web 应用的性能问题
    关于 SAP 电商云首页加载时触发的 OCC API 请求
    SAP UI5 确保控件 id 全局唯一的实现方法
    SAP 电商云 Accelerator 和 Spartacus UI 的工作机制差异
    介绍一个好用的能让网页变成黑色背景的护眼 Chrome 扩展应用
    Chrome 开发者工具 performance 标签页的用法
    Client Side Cache 和 Server Side Cache 的区别
  • 原文地址:https://www.cnblogs.com/waw/p/2157190.html
Copyright © 2011-2022 走看看