为啥继承是强耦合的,而Java中的组合是松耦合的? [复制]

Posted

技术标签:

【中文标题】为啥继承是强耦合的,而Java中的组合是松耦合的? [复制]【英文标题】:why inheritance is strongly coupled where as composition is loosely coupled in Java? [duplicate]为什么继承是强耦合的,而Java中的组合是松耦合的? [复制] 【发布时间】:2013-10-09 10:20:01 【问题描述】:

我在设计模式中一次又一次地听到这个favor composition over inheritance。引用的一些原因是

1)Inheritance is strongly coupled where as composition is loosely coupled
2) Inheritance is compile time determined where as composition is run-time
3)Inheritance breaks encapsulation where as composition does not
4) anything else I am not aware of

对于像我这样的初学者来说,通过插图来了解继承和组合在上述几点方面的不同之处,这将是非常棒的。我已经阅读了各种讨论它们的 SO 链接,但是通过这些关键点的示例对 Java 初学者来说非常有用。

我认为清楚地理解差异比仅仅记住要点非常重要。

【问题讨论】:

上面编号的几个项目将受益于包含短语“可能”或“可能是”。 This nice article 会优雅地回答问题。 【参考方案1】:

很好的问题,对初学者来说很大,我想我应该首先提醒读者什么是继承和组合,然后继续解释Favor Composition over Inheritance到底是什么意思。利弊继承权

优点:

动态绑定和多态性的主要好处之一是 它们可以帮助使代码更容易更改。 新的实现很容易,因为大部分都是继承的。这很容易 修改或扩展正在重用的实现。

缺点:

打破封装,因为它向实现公开了一个子类 其超类的详细信息。 White-box 重用,因为超类的内部细节通常是 对子类可见。 如果实现 超类变化。从超类继承的实现可以 不会在运行时更改。

关于问题:

继承是强耦合的,而组合是松耦合的

继承会带来紧密耦合,只需对基类进行一次更改就可以破坏许多子类。但是何时使用以及如何检测我们需要继承或组合? 仅当满足以下所有条件时才使用继承(Coad 规则):

    子类表示is a special kind of 而不是is a role played by a。 子类的实例永远不需要成为另一个类的对象。 子类扩展,而不是覆盖或取消,责任 它的超类。 子类不扩展仅仅是一个 实用程序类。 对于实际问题域中的类,子类专门 角色、事务或设备。

继承是编译时间决定的,而组合是运行时间

编译后,您的基类代码将添加到每个子类中。

继承会破坏封装,而组合则不会

是的。现在您看到了继承的缺点。

底线是

确保继承对 is-a 关系建模 我的主要指导理念是只有在子类 is-a 超类时才应该使用继承。在上面的示例中,Apple 可能是-a Fruit,所以我倾向于使用继承。

当您认为存在 is-a 关系时,要问自己一个重要问题,即 is-a 关系是否会在应用程序的整个生命周期以及代码的生命周期中保持不变。例如,您可能认为EmployeePerson,而实际上Employee 代表Person 有时扮演的角色。如果这个人失业了怎么办?如果此人既是 Employee 又是 Supervisor 怎么办?这种无常的 is-a 关系通常应该用组合来建模。

不要仅仅为了代码重用而使用继承 如果您真正想要的只是重用代码并且看不到 is-a 关系,请使用组合。

不要仅仅为了获得多态性而使用继承 如果您真正想要的只是多态性,但没有自然的 is-a 关系,请使用带有接口的组合。优先使用组合而不是继承 :)

我直接从javaworld 拿走了它。

【讨论】:

看起来你是从javaworld.com/jw-11-1998/jw-11-techniques.html?page=3复制和粘贴的。 这是一个一般性问题,我会尝试逐步解释。我会尽力帮助您。是的,答案的一部分只是复制和过去。 以我个人的经验,不得不处理遗留的 Enterprise 源代码,***地使用继承来进行代码重用,我被阻止在 Yo-yo problem,unit test was nearly impossible,继承 cased Rigidity,违反 LSP是一个已知的anti-pattern【参考方案2】:

在代码中使用具体类的扩展来表达继承。一旦你写了它,你就不能在不重写类的情况下改变它。我只能通过修改代码来改变。

public class Foo 
    public void doSomething()  System.out.println("I'm a Foo"); 


public class Bar extends Foo 
    public void doSomething()  super.doSomething(); 

我只能通过修改一个或两个类来改变Bar 所做的事情。

组合通常是基于接口的,这意味着您指定做什么,而不是如何。只需更改接口的实现,您就可以在不影响客户端的情况下更改方式。

public interface Foo 
    void doSomething();


public class FooImpl implements Foo 
    public void doSomething()  System.out.println("I'm a Foo"); 


public class Bar implements Foo 
    private Foo foo;

    public Bar(Foo f)  this.foo = f; 

    public void doSomething()  this.foo.doSomething(); 

在这种情况下,我可以通过传递 Foo 接口的不同实现来更改 Bar 的行为。

这是 Bob Martin 的 SOLID 原则之一:The Open/Closed Principle。

【讨论】:

即使有接口,实现接口的类也得实现接口的方法,这涉及到重写实现类中的实现..? 合成使用接口吗?你不能用具体的class吗? 最好基于接口,IMO。 IMO 你应该对委托发表评论。 OP,除非您设计一个组合框架,否则您将不得不更改代码。在这种情况下,对象的组合将完全基于数据,但您将受限于您正在使用/编写的框架。

以上是关于为啥继承是强耦合的,而Java中的组合是松耦合的? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

组合和封装

松耦合和紧耦合

微服务 继承与构成:如何选择

为啥返回一个迭代器低耦合(OOP)?

函数式编程思想:耦合和组合,第2部分

微服务之间调用的最佳设计