也许您刚刚注意到人们在最近几个月中一直在说这句话,但是对于优秀的程序员而言,知道它的时间要长得多。我肯定已经说了大约十年了。
这个概念的重点是继承有很大的概念开销。当您使用继承时,则每个单个方法调用中都隐含有一个调度。如果您有深层继承树,或者有多个调度,或者两者都有(甚至更糟),那么弄清楚特定方法在任何特定调用中将调度到的位置都可以成为皇家PITA。这使得对代码的正确推理更加复杂,并且使调试更加困难。
让我举一个简单的例子来说明。假设在继承树的深处,有人命名为method foo
。然后其他人出现并添加foo
到树的顶部,但是做了一些不同的事情。(这种情况在多重继承中更为常见。)现在,在根类中工作的人破坏了晦涩的子类,并且可能没有意识到。您可能具有100%的单元测试覆盖率,而不会注意到这种破坏,因为最顶层的人不会考虑测试子类,而针对子类的测试不会考虑对顶层创建的新方法进行测试。(诚然,有一些方法可以编写单元测试来解决此问题,但是在某些情况下,您不能轻易地以这种方式编写测试。)
相比之下,当您使用合成时,通常在每次呼叫时都将呼叫分配到的对象更加清楚。(好吧,如果您正在使用控制反转,例如通过依赖项注入,那么弄清楚调用的位置也可能会遇到问题。但是通常更容易弄清楚。)这使得推理起来更加容易。作为奖励,组成会导致方法彼此分离。上面的示例不应该在那里发生,因为子类将移至一些晦涩的组件,并且从来没有问题要调用foo
的对象是晦涩的组件还是主对象。
现在您绝对正确,继承和组合是服务两种不同类型事物的两个非常不同的工具。当然,继承会带来概念上的开销,但是,当继承是工作的正确工具时,继承所带来的概念上的开销要小于不使用继承并手动执行为您带来的工作。没有人知道他们在做什么,不会说您永远不要使用继承。但是请确保这是正确的做法。
不幸的是,许多开发人员学习了面向对象的软件,了解了继承,然后出去尽可能多地使用他们的新斧头。这意味着他们在组合是正确的工具的情况下尝试使用继承。希望他们会及时学习,但是经常不会发生这种情况,除非肢体移开一些,等等。事先告诉他们这是一个坏主意,这会加快学习过程并减少伤害。