【问题标题】:Interface and Inheritance - an Object Oriented Design Dilemma接口和继承——面向对象的设计困境
【发布时间】:2017-03-10 20:20:14
【问题描述】:

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

假设我们有一个父类 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() 添加到这个子类中,然后让DogCat 都继承自这个新的子类。这听起来很合理,直到您意识到还有许多其他此类异常,并且我们必须对每个异常重复此过程(这会将继承层次结构扩展至无止境)。

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

我们要做什么?

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

如果你仔细想想,这意味着所有 50 多只狗面包(假设有 50 只狗面包需要表现出这种行为)都需要添加工具 TailWaggler 关键字,因为只有一种 @ 987654368@ 没有表现出这种行为。这不仅意味着程序员需要进行大量额外的手动工作(在每个类的开头添加实现 TailWaggler这意味着所有后代都需要关注他们表现出的行为(如果我们将此行为添加到父类并扩展父类,情况就不会如此)。如果我们只有几个这样的案例,这可能很好,但如果我们有几十个或数百个这样的案例呢?最后,当我们添加新类型的狗子类时,最终将有一种Dog 是另一种不会表现出 Dog 父类行为之一的类型 - 所以这意味着我们需要缓慢但肯定地删除几乎(父)Dog 类的所有行为并将它们添加到接口?然后我们需要确保所有的子类都实现了几十个不同的接口。有人可能会建议我们将所有相关行为组合在一个界面中,但这只有在不同狗表现出的行为一致时才有可能 - 如果不是这种情况怎么办?)

谢谢!

【问题讨论】:

  • 忍受我。熊。 Bare 意味着赤身裸体。
  • 使用Mixins 是解决这个问题的一种方法——允许组合和代码重用。但是,Java 没有对 Mixins 的原生支持,您需要使用委托或静态实用程序来解决这个问题。
  • 如果您提供一个来自您处理的“复杂/抽象”案例的实际示例可能会更好。在我看来,您的 Animal、Dog、Cat 示例可能无法按原样描述您的情况。有了具体的例子,我们或许可以共同得出一个解决方案。
  • @MickMnemonic java 8 默认接口实现是一种 Mixins 形式。
  • @minus 是的,默认方法允许使用 mixins;但是当您阅读一些内容时,您会发现 java 背后的人确实建议以这种方式看待它们。

标签: java oop inheritance


【解决方案1】:

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

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

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

这意味着所有的后代都需要关心所有的小事 以及他们表现出的行为细节

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

  1. 为每个类手动实现委托:(

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

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

【讨论】:

    【解决方案2】:

    当您想要变形一个与您的父类具有相同类型并且具有相似行为的类时,可以使用继承。
    接口用于声明您的类的功能

    例如,Dogs、Cats 和 Mice 都是动物(相同类型)并且它们具有相似的行为(它们出生、长大、死亡、移动、进食……)。所以你的 Dog 类可以扩展 Animal。

    现在,您的接口声明了它们的功能。正如我们刚刚看到的,动物可以移动和进食,因此您的 Animal 类可以实现接口 MoverEater 等接口。 Dog、Cat 和 Mouse 会自动继承这些接口,但老鼠会吃奶酪,而狗和猫会吃肉。在这里你可以@Override(理解morph)实现行为来声明每个类可以吃什么。

    如果其他动物可以攀爬(猴子),您将直接在 Monkey 类上实现 Climber 接口。使其比标准动物稍微进化一些。


    对于尾随问题,您必须在 Dog 和 Cat 中实现 Tailwagger 接口,而不是在 Animal 中(并非所有动物都尾随)。当然,您不想重复代码,因此您还将创建另一个名为 StandardTailwag 的类,并将其用作 Dog 和 Cat 中的字段(composition)。然后,您必须将实现重定向到此类的方法,但如果您希望您的代码在将来更易于维护,那么这是要走的路。

    您还可以将 StandardTailwag 变形为 DogTailwagCatTailwag,并使用相同的 Tailwagger 接口轻松实现它们


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

    【讨论】:

    • 我理解将某些功能移动到界面的概念 - 但是正如我在 OP 中提到的那样,主要问题是在某个地方,其中一个后代将不需要父类 (Animal) 将阻止我们在 Animal 类中实现接口,并强制我们在每个子类中实现接口。这让我们回到了我提到的问题,即每个子类都需要关注它们将实现哪些接口。
    • 我不知道你的代码,但你总是可以注释类来指定特殊行为。运行时注释是你的朋友。
    【解决方案3】:

    这是一个非常广泛和主观的问题,所以我只能给你我的意见,仅此而已。

    我个人的原则是:“你写的代码越少越好”,但说实话实现简单是非常困难的。

    我尝试使继承尽可能浅,因为当你的模型发生变化时,深度继承往往会成为一个问题。

    然后我将接口与 handlers 一起使用,因此我没有使用 waggleTail 方法,而是使用了一个无状态的 TailWaggler 类来做摇摆的事情。

    我不认为所有可能的情况都有一个秘诀,我尽量让它尽可能简单,尽可能地可测试,然后你将(迟早)重构你的代码,如果测试是好的,不会太痛。

    【讨论】:

      【解决方案4】:

      对一个长问题的简短回答,所以在其他有更多精力的人加入之前不要接受这个。但我会如何做到这一点是有一个实现 TailWagger 接口的抽象 Dog 类,并有一个具体的 tailWag功能。

      接下来让您的所有狗都从 Dog 继承,包括实际上不会摇摆的狗。然后在那个特定的狗实现中,创建一个名为 tailWag 的新具体函数,该函数按照 InvalidStateException("This type of dog does not wag its tail") 的行抛出异常。

      使用这种方法,您可以得到一个摇尾的具体“tailWag”,以及一个表现不同的例外情况。

      【讨论】:

      • 这将违反 LSP,因为在不改变程序行为的情况下,将无法使用 ParticularDog(子类型)代替 Dog(超类型)。
      • 那就做点别的吧。嘘,这只是一个想法。您会注意到没有附加代码...
      猜你喜欢
      • 1970-01-01
      • 2021-05-31
      • 2019-04-12
      • 2014-12-24
      • 2019-09-29
      • 1970-01-01
      • 2015-10-29
      • 1970-01-01
      • 2010-09-15
      相关资源
      最近更新 更多