【问题标题】:How to move away from Inheritance如何摆脱继承
【发布时间】:2011-09-03 00:54:02
【问题描述】:

我在这里和其他论坛中搜索过,但找不到好的答案.. 我知道扩展课程并不是最好的做法。我应该更多地使用接口。我的问题是,通常我开始创建接口,然后转到抽象类,因为总是有一些我想在超类上实现的功能,这样我就不必在每个子类中复制它。 例如,我有一个 Vehicle 类以及 Car 和 Bike 子类。很多功能都可以在 Vehicle 类上实现,例如 Move() 和 Stop(),那么保持架构清洁、避免代码重复和使用接口而不是继承的最佳实践是什么? 非常感谢!

(如果你不知道我为什么要问这个,你可以阅读这篇有趣的文章:http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html

【问题讨论】:

  • 你的建议似乎很合理。接口 -> 抽象 -> 等等。

标签: oop inheritance architecture interface extend


【解决方案1】:

继承允许代码重用和可替换性,但限制了多态性。组合允许代码重用但不允许可替代性。接口允许可替代性,但不允许代码重用。

决定使用继承、组合还是接口,归结为几个简单的原则:

  1. 如果同时需要代码重用和可替换性,并且对多态性的限制还不错,请使用继承。

  2. 如果需要重用代码而不是可替代性,请使用组合。

  3. 如果需要可替换性,但不需要代​​码重用,或者如果继承对多态性施加的限制比重复代码更糟糕,请使用接口。

  4. 如果需要可替换性和代码重用,但不能接受多态性所施加的限制,请使用接口来包装封装对象。

  5. 如果需要可替换性和代码重用,并且多态性强加的限制不会立即造成任何问题,但可能会对未来的可替换类造成问题,则派生一个实现接口的模型基类,并让这些类派生自它这样做。避免使用类类型的变量和参数,尽管使用接口来代替。如果你这样做了,并且需要一个不能很好地从模型基类派生的可替代类,那么新类可以实现接口而不必从基类继承;如果需要,它可以通过包装模型类型派生的封装实例来实现接口。

在决定未来的可替代类是否难以从基类派生时可能需要判断。但是,当需要可替代性时,我倾向于认为方法 5 经常提供所有世界中最好的。它通常比单独使用接口便宜,并且比单独使用继承贵不了多少。如果将来需要可替换但不能从基类派生的类,则可能需要将代码转换为使用方法#5。从一开始就使用方法#5 可以避免以后重构代码。 (当然,如果永远不需要替换不能从基类派生的类,那么额外的成本——尽管可能很小——最终可能是不必要的。

【讨论】:

    【解决方案2】:

    我认为,接口有时也是邪恶的。它们可以避免多重继承。

    但是如果我们将接口与抽象类进行比较,那么抽象类总是比接口多。接口始终是类的某个方面——某种观点,而不是作为一个类的整体。

    所以我认为您不应该避免继承并在任何地方使用 iterfaces —— 应该保持平衡。

    【讨论】:

      【解决方案3】:

      如果您牢记接口和类之间的根本区别,则可以更轻松地决定使用哪一个。不同之处在于接口仅代表所涉及对象之间的协议(通常是行为),而抽象类代表一些涉及某些部分(数据)的未完成构造。在汽车示例中,界面本质上是通用汽车的蓝图。抽象类就像预制的特定模型车身,需要用剩余零件填充才能获得最终产品。接口甚至不必使用 Java - 它不会改变任何东西 - 仍然是蓝图。

      通常,您会在特定的实现框架中使用抽象类来为其消费者提供一些基本功能。如果您只是声明您从不使用抽象类来支持接口 - 从实际角度来看这是完全错误的。如果您需要 90% 的相同代码的相同接口的 10 个实现怎么办。复制代码 10 次?好的,也许你会在这里使用抽象类,但把接口放在它上面。但是,如果您从不打算将您的类层次结构提供给外部消费者,您为什么要这样做呢? 我在非常广泛的意义上使用外部这个词 - 它可以是您项目或远程消费者中的不同包。

      归根结底,其中许多都是偏好和个人经历,但我不同意大多数笼统的陈述,例如 extends is evil。我也不喜欢使用额外的类(接口或抽象),除非设计的特定部分需要。

      只要我的两分钱。

      【讨论】:

      • 感谢您的回答亚历克斯,您有一个使概念透明的好方法,它确实让我更清楚。我同意这篇文章太激进了,可能会引起注意:)
      【解决方案4】:

      同意 tofutim - 在您当前的示例中,在 Vehicle 上移动和停止是合理的。

      阅读了这篇文章 - 我认为它使用强大的语言来推动观点......记住 - 继承是帮助完成工作的工具。

      但是,如果我们假设在这种情况下由于任何原因您不能/不会使用该工具,您可以首先将其分解为带有帮助对象和/或访问者的小界面...

      例如 - 车辆类型包括潜艇、船、飞机、汽车和自行车。你可以把它分解成接口...... 可移动 + 前锋() + 向后() + 左() + 右()

      IF 可浮动 + 码头()

      ISink() + 吹气()

      飞飞() +起飞() + 土地()

      然后您的类可以聚合您刚刚定义的过多接口。

      问题在于,您最终可能会在汽车/自行车类中为 IMoveable.Left() 和 IMoveable.Right() 复制一些代码。您可以将其分解为辅助方法并聚合辅助方法...但如果您按照其逻辑结论进行操作,您最终仍会将许多东西重构回基类。

      继承和聚合是工具……它们都不是“邪恶的”。

      希望对您有所帮助。

      【讨论】:

      • 嘿彼得感谢您的回答。所以你认为为每种行为提供这些接口会是有益的吗?你会做类似的事情:class Car extends LandVehicle implements IMoveable 吗?
      • 经典答案 - “视情况而定”。一般来说,你要小心深层次的继承——但同时——如果它真的是陆地车辆,那么一定要从那里继承。如果所有陆地载具都是可移动的,那么就把界面放在上面。
      【解决方案5】:

      继承(“扩展类”)对类设计有很大的限制,我不确定使用接口代替继承是否是最好的主意,因为它无法通过 DRY 测试。

      如今,组合比继承更受青睐,因此您可以考虑这篇文章:Prefer composition over inheritance?

      【讨论】:

      • 谢谢 Rob,我不知道组合是一种正式的模式,这很有趣,谢谢你的链接!
      【解决方案6】:
      • 不要相信你读到的所有内容

        • 很多时候,继承还不错,其实对于数据隐藏来说,可能是个好主意。
      • 基本上,只有在创建非常小的类树时才使用“仅接口”策略,否则,我保证会很痛苦。假设你有一个人“类”(有eat()sleep),并且有两个子类,数学家(有doProblem())和EngineerbuildSomething()),然后使用接口。如果你需要一个 Car 类,然后是 56 种类型的汽车,那么就使用继承。

      恕我直言。

      【讨论】:

      • 嗯,好的,谢谢,所以在那个“Person”示例中,您将使用eat() 和sleep() 编写一个“Person”接口?这些方法将在哪里实施?数学家和工程师?
      • 是的,数学家和工程师。
      • 但这不会太多余了吧?
      【解决方案7】:

      有趣的问题。每个人都有不同的方法。但这一切都基于个人经验和选择。

      通常,我从一个接口开始,然后让一个抽象类继承该接口。并在那里实现共同的行动,让其他人由继承这个类的人来实现。

      根据经验,这很少有优势,
      1.在函数调用过程中,您可以将元素作为接口类型或抽象类类型传递。
      2.ID、Names等常用变量可以放在抽象类中。
      3.易于维护。例如,如果您想实现一个新接口,那么只需在抽象中快速实现即可。

      【讨论】:

      • 这很简单,涵盖了很多情况,我喜欢这种方法,谢谢!
      【解决方案8】:

      您需要针对您的具体案例还是一般情况的答案?在您描述的情况下,使用 Abstract 类没有任何问题。当所有客户端都需要为 Move() 和 Stop() 实现完全相同的代码时,使用接口是没有意义的。

      【讨论】:

      • 谢谢亚历克斯,我正在寻找一个通用的架构答案,这只是一个例子(我猜这个问题太简单了..)
      猜你喜欢
      • 1970-01-01
      • 2013-07-24
      • 1970-01-01
      • 1970-01-01
      • 2011-03-09
      • 1970-01-01
      • 2014-01-28
      • 2013-10-31
      • 2020-10-19
      相关资源
      最近更新 更多