接口和继承 - 面向对象的设计困境

时间:2017-03-10 20:20:15

标签: java oop inheritance

我无法理解何时/为什么要实现继承以及何时/为什么要通过接口实现继承。当我解释时,请耐心等待。

假设我们有一个父类Animal,我们希望用3个子类扩展它:DogCatMouse

假设所有动物都能够eat()sleep()scratch()move()。并且Dog能够pant()。有了这些知识,我们将继续将前4个行为添加到Animal超类,并DogCatMouse扩展Animal。我们还会将方法pant()添加到Dog类,仅限于狗pant()

如果我们希望添加另一个名为waggleTail()的方法,但只有CatDog表现出此行为,会发生什么。我们无法将此行为添加到Animal,因此Mouse也将继承行为(并且鼠标不会摇摆它的尾部)。另一种方法是将方法waggleTail()添加到DogCat类,但不添加到Mouse类。然而,这种方法没有意义,因为我们会通过编写方法waggleTail()两次来违反DRY原则(不要重复自己)。我们想要编写一次,每次只编写一次。

也许我们可以通过创建一个继承自Animal TailWagglingAnimal的新子类来解决这个问题,将方法waggleTail()添加到此子类,然后同时拥有Dog并且Cat继承自这个新的子类。这听起来很合理,直到你意识到还有其他几十个这样的异常,我们必须一次又一次地重复这个过程(这会将继承层次结构扩展到无止境)。

此外,如果我们有一种特定的Dog(让我们称之为“Coton de Tulear”)展示Dog的所有其他行为(如气喘吁吁),除了它不会摇尾巴。如果我们将“Coton de Tulear”直接从Animal继承,那么它将无法喘气()。如果我们从Dog继承它,那么就可以摆动它的尾部(bec Dog extends TailWagglingAnimal)。如果我们直接Dog扩展Animal,然后创建一个名为TailWagglingDog的新子类(与TailWagglingAnimal相同),那么Cat将无法继承此行为(因此我们' d需要在Cat层次结构中某处复制违反DRY原则的行为。)

我们做什么?

基于stackoverflow上的几十个线程(以及几个OO设计书籍),建议从Dog类中删除方法waggleTail()并将其添加到接口。让我们调用接口TailWaggler,然后让所有的狗(“Coton de Tulear”除外)实现这个接口。但是,我无法理解为什么/如何有用。

如果您考虑一下,这意味着所有50多只狗的面包(让我们假设有50只狗的面包需要表现出这种行为)需要添加工具TailWaggler关键字只是一种Dog的{​​{1}}没有表现出这种行为。这不仅意味着程序员的大量额外手动工作(将实现TailWaggler添加到每个类的开头)这意味着所有后代都需要关注所有的小而小的他们展示的行为的详细信息(如果我们将此行为添加到父类并扩展父类,则不会出现这种情况)。如果我们只有少数这样的情况,这可能没问题,但如果我们有几十个或几百个这样的情况呢?最后,当我们添加类型为dog的新类型的子类时,最终会有一种Dog是另一种不会表现出Dog父类行为的行为 - 所以这意味着我们需要缓慢但肯定的从(父)Dog类中删除几乎所有行为并将它们添加到接口?然后我们需要确保所有子类都实现了许多不同的接口。有人可能会建议我们在一个界面中对所有相关行为进行分组,但这只有在不同的狗表现出来的行为是统一的情况下才有可能 - 如果不是这样的话会怎样?)

谢谢!

4 个答案:

答案 0 :(得分:3)

  

然后我们需要确保所有子类都实现了几十个   不同的接口

  1. 如果您的类需要实现太多接口,请检查它是否违反了单一责任原则。考虑将班级分成较小的班级。

  2. 实现几个小接口而不是大接口符合接口隔离原则,这会导致一些积极的后果。

  3.   

    这意味着所有后代都需要关注所有的后果   他们展示的行为的细节

    这更多是关于实施困难。多重继承或自动委派可以在这里提供帮助。由于我们在Java中没有,我们必须在其他选项之间进行选择:

    1. 为每个类手动实施委派:(

    2. 如果实现不复杂,请使用Java 8接口。

    3. 使用代码生成库自动生成委托代码(例如,查看lombok库@Delegate feature https://projectlombok.org/features/experimental/Delegate.html

答案 1 :(得分:2)

当您想要变形与您的父类具有相同类型且具有相似行为的类时,将使用继承。
界面用于声明您班级的功能

例如,狗,猫和小鼠都是动物(相同类型)并且它们具有相似的行为(它们会出生,长大,死亡,移动,进食......)。所以你的Dog课程可以扩展Animal。

现在,您的接口声明了它们的功能。就像我们刚刚看到的那样,动物可以移动和吃东西,所以你的Animal类可以实现接口 Mover Eater 。自动地,狗,猫和老鼠将继承这些界面,但是老鼠会吃奶酪而狗和猫会吃肉。在这里你可以@Override(理解变形)实现行为来声明每个类可以吃的东西。

如果另一只动物可以爬(猴子),你将直接在Monkey类上实现 Climber 界面。使它比标准动物稍微进化。

对于你的尾随问题,你必须在Dog and Cat中实现 Tailwagger 界面,而不是在动物中(所有动物都不是尾巴)。当然,您不想重复代码,因此您还将创建另一个名为 StandardTailwag 的类,并将其用作Dog和Cat中的字段(组合)。然后,您必须将实现重定向到此类的方法,但如果您希望将来更容易维护代码,则可以采用这种方法。

您还可以将StandardTailwag变形为 DogTailwag CatTailwag ,并使用相同的Tailwagger界面轻松实现它们

请注意,您可以在Java 8的界面中编写默认代码和方法,但不建议这样做。

答案 2 :(得分:2)

这是一个非常广泛和主观的问题,所以我可以给你我的意见,而不是更多。

我个人的原则是:"你编写的代码越少越好,但要真实地实现简单是非常困难的。

我尽量让继承尽可能浅,因为深度继承往往会在模型发生变化时成为问题。

然后我使用与处理程序的接口,所以我没有一个方法waggleTail,而是有一个无状态的TailWaggler类来处理摇摆不定的事情。

我不认为每个可能的情况都有一个配方,我尽量保持尽可能简单,尽可能可测试,然后你会(迟早)重构你的代码,如果测试很好,不会太痛苦。

答案 3 :(得分:0)

对一个很长的问题的简短回答,所以不要接受这个,直到其他人有更多的能量进入。但我怎么做才有一个抽象的Dog类来实现TailWagger接口,并且有一个具体的尾巴功能。

接下来让你的所有狗都继承自Dog,包括那些实际上并不存在的狗。然后在那个特定的dog实现中,创建一个名为tailWag的新的具体函数,它会沿着InvalidStateException行引发异常("这种类型的狗不会摇尾巴#34;)。

使用这种方法,你有一个具体的" tailWag"摇尾巴,以及一种表现不同的特殊情况。