组合与继承是经常被问到的面试问题之一。你可能也听说过要优先选择组合而不是继承。
组合与继承
组合和继承都是面向对象编程概念。它们与特定的编程语言如Java等没有直接关联。在我们编程上比较组合和继承之前,让我们快速了解一下它们的定义。
组合
组合是面向对象编程中实现对象之间拥有关系的设计技术。在Java中,通过使用其他对象的实例变量来实现组合。例如,在Java面向对象编程中,拥有工作的人可以如下实现:
package com.journaldev.composition;
public class Job {
// 变量、方法等
}
package com.journaldev.composition;
public class Person {
// 组合拥有关系
private Job job;
// 变量、方法、构造函数等 面向对象
继承
继承是面向对象编程中的设计技术,用于实现对象之间的 is-a 关系。在 Java 中,继承是使用 extends 关键字实现的。例如,在 Java 编程中,Cat 是 Animal 的关系将被实现如下。
package com.journaldev.inheritance;
public class Animal {
// 变量、方法等。
}
package com.journaldev.inheritance;
public class Cat extends Animal{
}
组合优于继承
组合和继承都通过不同的方式促进代码重用。那么该选择哪一个呢?如何比较组合和继承。你一定听说过在编程中应该优先选择组合而不是继承。让我们看看一些原因,这将帮助你选择组合还是继承。
-
继承是紧密耦合的,而组合是松散耦合的。假设我们有以下带有继承关系的类。
package com.journaldev.java.examples; public class ClassA { public void foo(){ } } class ClassB extends ClassA{ public void bar(){ } }
为简单起见,我们将超类和子类都放在一个单一的包中。但通常它们会在不同的代码库中。可能会有许多类扩展超类ClassA。这种情况的一个非常常见的例子是扩展Exception类。现在假设ClassA的实现如下所示,添加了一个新方法bar()。
package com.journaldev.java.examples; public class ClassA { public void foo(){ } public int bar(){ return 0; } }
一旦您开始使用新的ClassA实现,您将在ClassB中收到编译时错误,提示
返回类型与ClassA.bar()不兼容
。解决方案是更改超类或子类的bar()方法以使它们兼容。如果您使用组合而不是继承,您将永远不会遇到此问题。使用组合的ClassB实现的一个简单示例如下。class ClassB{ ClassA classA = new ClassA(); public void bar(){ classA.foo(); classA.bar(); } }
-
在继承中没有访问控制,而在组合中可以限制访问。我们将所有超类方法暴露给具有对子类访问权限的其他类。因此,如果引入新方法或超类中存在安全漏洞,子类将变得脆弱。由于在组合中我们选择使用哪些方法,它比继承更安全。例如,我们可以使用ClassB中的以下代码将ClassA的foo()方法暴露给其他类。
class ClassB { ClassA classA = new ClassA(); public void foo(){ classA.foo(); } public void bar(){ } }
这是组合优于继承的一个主要优势。
-
组合在多个子类场景中提供了调用方法的灵活性。例如,假设我们有以下继承场景。
abstract class Abs { abstract void foo(); } public class ClassA extends Abs{ public void foo(){ } } class ClassB extends Abs{ public void foo(){ } } class Test { ClassA a = new ClassA(); ClassB b = new ClassB(); public void test(){ a.foo(); b.foo(); } }
那么如果有更多的子类,通过为每个子类都创建一个实例,会使我们的代码变得混乱吗?不会,我们可以像下面这样重写Test类。
class Test { Abs obj = null; Test1(Abs o){ this.obj = o; } public void foo(){ this.obj.foo(); } }
这将使您能够根据构造函数中使用的对象使用任何子类。
-
组合优于继承的另一个好处是测试范围。在组合中进行单元测试很容易,因为我们知道我们从另一个类中使用了哪些方法。我们可以对其进行模拟测试,而在继承中,我们严重依赖于超类,并不知道超类的所有方法将被使用。因此,我们将不得不测试超类的所有方法。这是额外的工作,因为我们不得不因为继承而不必要地做这些工作。
关于组合与继承的比较就到这里了。你已经有足够的理由选择组合而不是继承。只有在确信超类不会被更改时才使用继承,否则就选择组合。
Source:
https://www.digitalocean.com/community/tutorials/composition-vs-inheritance