组合与继承
组合与继承是面试中经常被问到的问题之一。你一定也听说过使用组合而不是继承。
组合与继承
组合和继承都是面向对象编程概念。它们不与任何特定编程语言(如 Java)绑定。在我们从编程角度比较组合和继承之前,让我们先快速定义一下它们。
作品
组合是面向对象编程中的一种设计技术,用于实现对象之间的has-a关系。Java 中的组合是通过使用其他对象的实例变量来实现的。例如,在 Java 面向对象编程中,拥有一份工作的人的实现如下。
package com.journaldev.composition;
public class Job {
// variables, methods etc.
}
package com.journaldev.composition;
public class Person {
//composition has-a relationship
private Job job;
//variables, methods, constructors etc. object-oriented
遗产
继承是面向对象编程中实现对象之间is-a关系的设计技术。Java 中的继承使用 extends 关键字实现。例如,Java 编程中的 Cat 是 Animal 关系将按以下方式实现。
package com.journaldev.inheritance;
public class Animal {
// variables, methods etc.
}
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 中得到编译时错误
The return type is incompatible with ClassA.bar()
。解决方案是更改超类或子类 bar() 方法以使它们兼容。如果您使用 Composition 而不是继承,您将永远不会遇到这个问题。使用 Composition 实现 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(){ } }
这是组合相对于继承的一大优势。
-
Composition provides flexibility in invocation of methods that is useful with multiple subclass scenario. For example, let’s say we have below inheritance scenario.
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(); } }
So what if there are more subclasses, will composition make our code ugly by having one instance for each subclass? No, we can rewrite the Test class like below.
class Test { Abs obj = null; Test1(Abs o){ this.obj = o; } public void foo(){ this.obj.foo(); } }
This will give you the flexibility to use any subclass based on the object used in the constructor.
-
One more benefit of composition over inheritance is testing scope. Unit testing is easy in composition because we know what all methods we are using from another class. We can mock it up for testing whereas in inheritance we depend heavily on superclass and don’t know what all methods of superclass will be used. So we will have to test all the methods of the superclass. This is extra work and we need to do it unnecessarily because of inheritance.
That’s all for composition vs inheritance. You have got enough reasons to choose composition over inheritance. Use inheritance only when you are sure that superclass will not be changed, otherwise go for composition.