is-a java_Java中的继承和组合(Is-a vs Has-a关系)

1.概述

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

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

2.继承的基础知识

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

简单地说,通过继承,基类(也称为基类型)定义了给定类型的常见状态和行为,并允许子类(也称为子类型)提供该状态和行为的专用版本。

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

这是Person类:

public class Person {

private final String name;

// other fields, standard constructors, getters

}

这些是子类:

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

}

另外,让我们创建一个单元测试来验证Waitress和Actress类的实例也是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);

}

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

这可能会让我们问:在哪些用例中继承正确的方法?

如果子类型满足“is-a”条件并且主要在类层次结构中提供附加功能,则继承是可行的方法。

当然,只要重写的方法保留Liskov替换原则所提倡的基本类型/子类型可替换性,就允许方法重写。

另外,我们应该记住,子类型继承了基类型的API,这在某些情况下可能过度或仅仅是不合需要的。

否则,我们应该使用构图。

3.设计模式中的继承

虽然我们的共识是我们应该尽可能地支持组合而不是继承,但是有一些典型的用例,其中继承有其自身的地位。

3.1.图层超类型模式

在这种情况下,我们使用继承将公共代码基于每层移动到基类(超类型)。

以下是域层中此模式的基本实现:

public class Entity {

protected long id;

// setters

}

public class User extends Entity {

// additional fields and methods

}

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

3.2.模板方法模式

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

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.作文的基础知识

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

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

此外,组合是最强的关联**形式,这意味着当一个对象被破坏时,由一个对象组成或包含的对象也被破坏**。

为了更好地理解组合的工作原理,我们假设我们需要使用代表计算机的对象。

计算机由不同的部分组成,包括微处理器,存储器,声卡等,因此我们可以将计算机及其每个部分建模为单独的类。

以下是Computer类的简单实现:

public class Computer {

private Processor processor;

private Memory memory;

private SoundCard soundCard;

// standard getters/setters/constructors

public Optional getSoundCard() {

return Optional.ofNullable(soundCard);

}

}

以下类为微处理器,内存和声卡建模(为简洁起见,省略了接口):

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

}

很容易理解推动组合超越继承的动机。在每个可以在给定类和其他类之间建立语义正确的“has-a”关系的场景中,组合是正确的选择。

在上面的示例中,计算机满足“has-a”条件,其中包含对其零件进行建模的类。

还值得注意的是,在这种情况下,当且仅当对象不能在另一个Computer对象中重用时,包含Computer对象才拥有所包含对象的所有权。如果他们可以,我们将使用聚合而不是组合,其中不隐含所有权。

5.没有抽象的构图

或者,我们可以通过硬编码计算机类的依赖关系来定义组合关系,而不是在构造函数中声明它们:

public class Computer {

private StandardProcessor processor

= new StandardProcessor("Intel I3");

private StandardMemory memory

= new StandardMemory("Kingston", "1TB");

// additional fields / methods

}

当然,这将是一种严格的,紧密耦合的设计,因为我们将使计算机强烈依赖于处理器和内存的特定实现。

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

通过基于接口的初始设计,我们获得了松散耦合设计,这也更容易测试。

六,结论

在本文中,我们学习了Java中继承和组合的基础知识,并深入探讨了两种关系之间的差异(“is-a”与“has-a”)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值