我听说它说"设计继承"是"困难的",但我从来没有发现过这种情况.任何人(以及任何人,我的意思是Jon Skeet)都可以解释为什么这可能是困难的,陷阱/障碍/问题是什么,为什么凡人的程序员不应该尝试它,只是让他们的课程密封以保护无辜者?
好吧,我嘲笑后者 - 但我很想知道是否有人(包括Jon)真的遇到"继承设计"的困难.我真的从来没有把它视为一个问题,但也许我忽略了一些我认为理所当然的东西 - 或者在没有意识到的情况下搞砸了什么!
编辑:感谢到目前为止所有优秀的答案.我相信共识是,对于典型的应用程序类(WinForm子类,一次性实用程序类等),不需要考虑任何类型的重用,更不用说通过继承重用了,而对于库类,考虑重用是至关重要的.通过设计中的继承.
我并没有真的想到一个WinForm类来实现一个GUI对话框,作为一个有人可能会重用的类 - 我有点认为它是一个一次性的对象.但从技术上讲,它是一个类,有人可能会继承它 - 但它不太可能.
很多大规模的发展,我所做的一直是基础库和框架类库,因此设计为通过继承的重用是至关重要的-我只是从来没有认为这是"难",它只是为.;-)
但我也从未考虑过与WinForms等常见应用任务的"一次性"类别相比.
当然,欢迎设计继承的更多提示和陷阱; 我也试着投入一些.
我只是回答你应该看看我在继承java.util.Properties时遇到的问题,而不是过多地重复它.我倾向于尽可能少地避免设计继承的许多问题.以下是这些问题的一些想法:
正确实现相等是一种痛苦(或可能是不可能的),除非你将它限制为"两个对象必须具有完全相同的类型".困难在于a.Equals(b)
暗示的对称要求b.Equals(a)
.如果a
并且b
分别是"10x10平方"和" 红色 10x10平方",那么很a
可能认为它b
等于它 - 在许多情况下,这可能是你真正想要测试的所有内容.但是b
知道它有颜色而a
不是......
每当你在实现中调用虚方法时,你必须记录这种关系,永远不要改变它.从类派生的人需要读取的文件,太-否则他们可能会全面贯彻落实调用的方法,迅速导致X调用Y,它调用X这就要求Y.这是我的主要问题的堆栈溢出-在许多您必须记录您的实施情况,导致缺乏灵活性.它显著减轻,如果你永远不会调用一个虚拟的方法从另一个,但你仍然有记录甚至从非虚拟的任何其他调用虚拟方法,并不会改变在这个意义上的实现.
即使没有一些未知代码成为执行时类的一部分,线程安全也很难实现.您不仅需要记录类的线程模型,还可能必须将锁(等)公开给派生类,以便它们可以以相同的方式保持线程安全.
在保持原始基类的合同范围内时,考虑哪种特化是有效的.在什么方面可以覆盖方法,以便调用者不需要知道专业化,只是它有效?java.util.Properties是一个很糟糕的特殊化的好例子 - 调用者不能只把它当作普通的Hashtable,因为它应该只有键和值的字符串.
如果一个类型是不可变的,它不应该允许继承 - 因为子类很容易变成可变的.然后可能会出现奇怪的情况.
你应该实现某种克隆能力吗?如果不这样做,可能会使子类更难以正确克隆.有时成员克隆是足够好的,但有时候它可能没有意义.
如果您不提供任何虚拟成员,则可能相当安全 - 但此时任何子类都提供额外的不同功能,而不是专门化现有功能.这不一定是坏事,但它不像继承的最初目的.
其中有许多是少得多的应用程序构建比类库设计师的一个问题:如果你知道你和你的同事都将是唯一的人曾经从你的类型派生的,那么你就可以少了很多向上逃脱前脸的设计 - 如果你在以后的日子需要你可以修复的子类.
顺便提一下,这些只是袖手旁观.如果我想的话,我可能会想出更多.乔希布洛赫所说的都很好有效的Java 2,顺便说一句.
我认为要点在于乔恩的回答中的倒数第二段:大多数人从应用程序设计的角度考虑“为继承而设计”,即他们只是希望它可以工作,如果不能,它可以是固定的。
但是,将可继承的类设计为供其他人使用的API是完全不同的野兽-因为随后,受保护的字段和大量的实现细节隐含地成为API合同的一部分,使用该API的人们必须理解,如果不使用API破坏代码,则无法更改。如果您在设计或实现中犯了一个错误,则很可能无法在不破坏依赖于该代码的代码的情况下对其进行修复。