Inheritance and Composition (Is-a vs Has-a relationship) in Java – Java中的继承和组合(Is-a与Has-a关系)

最后修改: 2018年 4月 10日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Inheritance and composition — along with abstraction, encapsulation, and polymorphism — are cornerstones of object-oriented programming (OOP).

继承和组合–以及抽象、封装和多态性–是面向对象编程(OOP)的基石。

In this tutorial, we’ll cover the basics of inheritance and composition, and we’ll focus strongly on spotting the differences between the two types of relationships.

在本教程中,我们将介绍继承和组成的基本知识,我们将重点关注发现这两类关系之间的差异。

2. Inheritance’s Basics

2.继承的基础知识

Inheritance is a powerful yet overused and misused mechanism.

继承是一个强大的但被过度使用和滥用的机制。

Simply put, with inheritance, a base class (a.k.a. base type) defines the state and behavior common for a given type and lets the subclasses (a.k.a. subtypes) provide specialized versions of that state and behavior.

简单地说,通过继承,基类(又称基类型)定义了一个给定类型的通用状态和行为,并让子类(又称子类型)提供该状态和行为的专门版本。

To have a clear idea on how to work with inheritance, let’s create a naive example: a base class Person that defines the common fields and methods for a person, while the subclasses Waitress and Actress provide additional, fine-grained method implementations.

为了清楚地了解如何使用继承,让我们创建一个天真的例子:一个基类Person定义了一个人的公共字段和方法,而子类WaitressActress提供了额外的、细粒度的方法实现。

Here’s the Person class:

这里是Person类。

public class Person {
    private final String name;

    // other fields, standard constructors, getters
}

And these are the subclasses:

而这些是子类。

public class Waitress extends Person {

    public String serveStarter(String starter) {
        return "Serving a " + starter;
    }
    
    // additional methods/constructors
}
public class Actress extends Person {
    
    public String readScript(String movie) {
        return "Reading the script of " + movie;
    } 
    
    // additional methods/constructors
}

In addition, let’s create a unit test to verify that instances of the Waitress and Actress classes are also instances of Person, thus showing that the “is-a” condition is met at the type level:

此外,让我们创建一个单元测试来验证WaitressActress类的实例也是Person的实例,从而表明在类型级别上满足 “is-a “条件。

@Test
public void givenWaitressInstance_whenCheckedType_thenIsInstanceOfPerson() {
    assertThat(new Waitress("Mary", "mary@domain.com", 22))
      .isInstanceOf(Person.class);
}
    
@Test
public void givenActressInstance_whenCheckedType_thenIsInstanceOfPerson() {
    assertThat(new Actress("Susan", "susan@domain.com", 30))
      .isInstanceOf(Person.class);
}

It’s important to stress here the semantic facet of inheritance. Aside from reusing the implementation of the Person class, we’ve created a well-defined “is-a” relationship between the base type Person and the subtypes Waitress and Actress. Waitresses and actresses are, effectively, persons.

这里需要强调的是继承的语义方面。除了重复使用Person类的实现之外,我们在基类型Person和子类型WaitressActress之间建立了一个明确的 “is-a “关系。女服务员和女演员实际上都是人。

This may cause us to ask: in which use cases is inheritance the right approach to take?

这可能会引起我们的疑问。在哪些用例中,继承是正确的做法?

If subtypes fulfill the “is-a” condition and mainly provide additive functionality further down the classes hierarchy, then inheritance is the way to go.

如果子类型满足了 “is-a “的条件,并且主要是为类的层次结构进一步提供附加功能, 那么继承就是最好的办法。

Of course, method overriding is allowed as long as the overridden methods preserve the base type/subtype substitutability promoted by the Liskov Substitution Principle.

当然,只要被覆盖的方法保留了Liskov替代原则所提倡的基础类型/子类型的可替代性,方法的覆盖是允许的。

Additionally, we should keep in mind that the subtypes inherit the base type’s API, which is some cases may be overkill or merely undesirable.

此外,我们应该记住,子类型继承了基类型的API,这在某些情况下可能是矫枉过正或仅仅是不可取的。

Otherwise, we should use composition instead.

否则,我们应该用组合来代替。

3. Inheritance in Design Patterns

3.设计模式中的继承性

While the consensus is that we should favor composition over inheritance whenever possible, there are a few typical use cases where inheritance has its place.

虽然大家都认为,只要有可能,我们就应该支持组合而不是继承,但在一些典型的用例中,继承也有其存在的意义。

3.1. The Layer Supertype Pattern

3.1.层的超类型模式

In this case, we use inheritance to move common code to a base class (the supertype), on a per-layer basis.

在这种情况下,我们使用继承来将普通代码转移到基类(超类型),在每一层的基础上

Here’s a basic implementation of this pattern in the domain layer:

下面是这种模式在领域层的基本实现。

public class Entity {
    
    protected long id;
    
    // setters
}
public class User extends Entity {
    
    // additional fields and methods   
}

We can apply the same approach to the other layers in the system, such as the service and persistence layers.

我们可以将同样的方法应用于系统中的其他层,如服务层和持久层。

3.2. The Template Method Pattern

3.2.模板方法模式

In the template method pattern, we can use a base class to define the invariant parts of an algorithm, and then implement the variant parts in the subclasses:

在模板方法模式中,我们可以使用一个基类来定义算法的不变部分,然后在子类中实现变体部分

public abstract class ComputerBuilder {
    
    public final Computer buildComputer() {
        addProcessor();
        addMemory();
    }
    
    public abstract void addProcessor();
    
    public abstract void addMemory();
}
public class StandardComputerBuilder extends ComputerBuilder {

    @Override
    public void addProcessor() {
        // method implementation
    }
    
    @Override
    public void addMemory() {
        // method implementation
    }
}

4. Composition’s Basics

4.构图的基本原理

The composition is another mechanism provided by OOP for reusing implementation.

组合是OOP提供的另一种重用实现的机制。

In a nutshell, composition allows us to model objects that are made up of other objects, thus defining a “has-a” relationship between them.

简而言之,组合允许我们对由其他对象组成的对象进行建模,从而定义了它们之间的 “有-a “关系。

Furthermore, the composition is the strongest form of association, which means that the object(s) that compose or are contained by one object are destroyed too when that object is destroyed.

此外,组合是关联的最强形式,这意味着组成一个对象或被一个对象包含的对象在该对象被销毁时也被销毁

To better understand how composition works, let’s suppose that we need to work with objects that represent computers.

为了更好地理解组合的工作原理,让我们假设我们需要与代表计算机的对象一起工作.

A computer is composed of different parts, including the microprocessor, the memory, a sound card and so forth, so we can model both the computer and each of its parts as individual classes.

一台计算机是由不同的部分组成的,包括微处理器、内存、声卡等等,所以我们可以把计算机和它的每个部分都建模为单独的类。

Here’s how a simple implementation of the Computer class might look:

下面是Computer类的一个简单实现。

public class Computer {

    private Processor processor;
    private Memory memory;
    private SoundCard soundCard;

    // standard getters/setters/constructors
    
    public Optional<SoundCard> getSoundCard() {
        return Optional.ofNullable(soundCard);
    }
}

The following classes model a microprocessor, the memory, and a sound card (interfaces are omitted for brevity’s sake):

下面的类对一个微处理器、内存和声卡进行了建模(为了简洁起见,省略了接口)。

public class StandardProcessor implements Processor {

    private String model;
    
    // standard getters/setters
}
public class StandardMemory implements Memory {
    
    private String brand;
    private String size;
    
    // standard constructors, getters, toString
}
public class StandardSoundCard implements SoundCard {
    
    private String brand;

    // standard constructors, getters, toString
}

It’s easy to understand the motivations behind pushing composition over inheritance. In every scenario where it’s possible to establish a semantically correct “has-a” relationship between a given class and others, the composition is the right choice to make.

我们很容易理解推动组合而不是继承背后的动机。在每一个有可能在一个给定的类和其他类之间建立语义正确的 “具有-a “关系的情况下,组合是正确的选择。

In the above example, Computer meets the “has-a” condition with the classes that model its parts.

在上面的例子中,Computer与模拟其部分的类一起满足 “有-a “的条件。

It’s also worth noting that in this case, the containing Computer object has ownership of the contained objects if and only if the objects can’t be reused within another Computer object. If they can, we’d be using aggregation, rather than composition, where ownership isn’t implied.

值得注意的是,在这种情况下,包含Computer的对象对所包含的对象有所有权,当且仅当这些对象不能在另一个Computer对象中被重用。

5. Composition Without Abstraction

5.没有抽象的构成

Alternatively, we could’ve defined the composition relationship by hard-coding the dependencies of the Computer class, instead of declaring them in the constructor:

另外,我们可以通过硬编码来定义Computer类的依赖关系,而不是在构造函数中声明它们。

public class Computer {

    private StandardProcessor processor
      = new StandardProcessor("Intel I3");
    private StandardMemory memory
      = new StandardMemory("Kingston", "1TB");
    
    // additional fields / methods
}

Of course, this would be a rigid, tightly-coupled design, as we’d be making Computer strongly dependent on specific implementations of Processor and Memory.

当然,这将是一个僵化的、紧密耦合的设计,因为我们将使Computer强烈依赖于ProcessorMemory的具体实现。

We wouldn’t be taking advantage of the level of abstraction provided by interfaces and dependency injection.

我们不会利用接口和依赖注入所提供的抽象级别。

With the initial design based on interfaces, we get a loosely-coupled design, which is also easier to test.

有了基于接口的初始设计,我们就得到了一个松散耦合的设计,这也更容易测试。

6. Conclusion

6.结论

In this article, we learned the fundamentals of inheritance and composition in Java, and we explored in depth the differences between the two types of relationships (“is-a” vs. “has-a”).

在这篇文章中,我们学习了Java中继承和组合的基本原理,并深入探讨了两种关系(”is-a “与 “has-a”)之间的区别。

As always, all the code samples shown in this tutorial are available over on GitHub.

一如既往,本教程中显示的所有代码样本都可以在GitHub上获得