继承(Inheritance)

继承(Inheritance)

继承允许一个类(称为子类派生类继承另一个类(称为父类基类)的属性和行为

简单来说:

继承通过让你在基类中只定义一次通用逻辑,然后在多个派生类中扩展或特化它,从而实现代码复用

这让软件更整洁、模块化,也更易于维护。

现实世界类比

想象一个 Web 应用中的 User(用户)系统:

  • 基类 User 持有通用属性,如 usernameemail,以及方法如 login()logout()
  • AdminCustomerVendor 这样的专属角色继承自 User,但会添加各自角色特有的行为。

所有专属用户类型都从 User继承通用数据和行为,同时可以扩展功能以适应各自的角色。

1. 为什么继承很重要

继承带来了多项好处,使其成为面向对象编程中一个强大的设计工具。

1. 代码复用性

它体现了 DRY(Don’t Repeat Yourself,不要重复自己) 原则。通用逻辑在父类中只写一次,就能在所有子类中共享,从而减少冗余。

2. 清晰的逻辑层次

它建立了清晰直观的层次结构,用于建模现实世界中的 “is-a”(是一种) 关系,例如 ElectricCar 是一种 Car,或 Admin 是一种 User

3. 易于维护

如果在共享逻辑中发现 bug 或需要改动,你只需在一个地方——即父类——修复它。所有子类都会自动继承这个修复。

4. 多态性

继承是多态性的前提条件,它允许将不同子类的对象当作父类的对象来对待。

2. 继承是如何运作的

当一个类继承自另一个类时:

  • 子类继承父类的所有非私有字段和方法
  • 子类可以重写(override)继承来的方法,以提供不同的实现。
  • 子类还可以通过添加新字段和新方法来扩展父类。

这同时实现了复用定制

示例

继承最基础的形式是:一个子类扩展父类,并在继承来的字段和方法之上添加新行为。下面是用继承构建的车辆层次结构。

这个 Vehicle 类定义了所有车辆共享的基本属性和通用行为。

接下来可以创建专门的车辆类型:

在这个例子中:

  • ElectricCarGasCar 都从 Vehicle继承makemodelstartEngine()stopEngine() 方法。
  • 每个子类都添加了各自类型特有的行为。
  • 这种结构反映了现实世界的关系:电动汽车是一种交通工具,燃油汽车也是。

下面给出 Vehicle 基类与 ElectricCar / GasCar 子类的 Java 实现。

车辆基类(Java)
class Vehicle {
    protected String make;
    protected String model;
    protected int year;

    public Vehicle(String make, String model, int year) {
        this.make = make;
        this.model = model;
        this.year = year;
    }

    public void startEngine() {
        System.out.println("Engine started");
    }

    public void stopEngine() {
        System.out.println("Engine stopped");
    }

    public void displayInfo() {
        System.out.println(year + " " + make + " " + model);
    }
}
子类:ElectricCar 与 GasCar(Java)
class ElectricCar extends Vehicle {
    private int batteryCapacity;

    public ElectricCar(String make, String model, int year, int batteryCapacity) {
        super(make, model, year);
        this.batteryCapacity = batteryCapacity;
    }

    public void chargeBattery() {
        System.out.println("Charging " + batteryCapacity + "kWh battery");
    }
}

class GasCar extends Vehicle {
    private double fuelTankSize;

    public GasCar(String make, String model, int year, double fuelTankSize) {
        super(make, model, year);
        this.fuelTankSize = fuelTankSize;
    }

    public void fillTank() {
        System.out.println("Filling " + fuelTankSize + "L fuel tank");
    }
}

3. 继承的类型

并非所有继承层次结构都一样。这里有几类常见模式,各自有不同的结构和取舍。

单继承是最简单的形式:一个子类扩展一个父类。ElectricCar extends Vehicle 这种关系就是单继承。这是最常见的模式,也是所有主流语言都支持的。

多层继承是指一个子类本身又成为父类。例如 Vehicle -> Car -> ElectricCar。每一层都增加更多特化。适度使用没问题,但很深的链(5 层以上)会变得脆弱且难以理解。

分层继承是指多个子类扩展同一个父类。我们前面的车辆例子中,ElectricCarGasCar 都扩展 Vehicle,这就是分层继承。这非常常见,也完全自然。

多重继承是指一个子类扩展多个父类。这里事情就开始变复杂了。只有 C++ 和 Python 直接支持多重继承。Java、C# 和 TypeScript 不支持。原因是什么?菱形继承问题(diamond problem)。

想象 ElectricCar 同时扩展 VehicleMachineVehicleMachine 都有一个 start() 方法。当你调用 electricCar.start() 时,到底执行哪个版本?来自 Vehicle 的?来自 Machine 的?还是两者都执行?

C++ 用虚继承(virtual inheritance)来处理,这种方式复杂且容易出错。Python 则采用方法解析顺序(MRO),一种明确界定的算法(C3 线性化)来决定哪个父类的方法优先。Java 和 C# 完全绕开了这个问题——它们只允许单类继承,你可以实现多个接口,但只能扩展一个类。

4. 何时使用继承

继承很强大,但不应滥用,只有在它真正能建模现实世界的关系时才使用。如果在设计早期做出错误判断,会导致代码难以修改、难以测试、也难以理解。

下面是一份实用的检查清单。

适合使用继承的情况:
  • 存在清晰的 “is-a”(是一种)关系(例如 Dog is an AnimalCar is a Vehicle)。如果你无法自然地说出"X 是一种 Y",那继承可能就是错误的工具,这类关系应该用组合。
  • 父类定义了子类应该共享的通用行为或数据。例如,所有车辆都有 startEngine() 方法,把它放在父类中就能避免在每个车辆类型里重复编写。
  • 子类不破坏父类预期的行为。如果有人持有一个指向 ElectricCarVehicle 引用,那么每个 Vehicle 方法都应当如预期般工作。
  • 你希望通过共享逻辑和结构来促进代码复用,且层次结构较浅(最多 2-3 层)。
应避免继承的情况:
  • 关系是 “has-a”(有一个)或 “uses-a”(使用一个),而非 “is-a”。Car 拥有一个 Engine,但它不是 EnginePrinter 使用一个 Logger,但它不是 Logger
  • 你希望动态地组合来自多个来源的行为。继承在编译期就把你锁定在单一父类上,而组合让你可以自由地混搭各个组件。
  • 你需要运行时的灵活性来替换行为。使用组合,你可以注入不同的实现(把 FileLogger 换成 ConsoleLogger)。而继承中,父子关系是固定的。
  • 你希望避免子类与父类内部之间的紧耦合。对父类的改动会向下波及层次结构中的每一个子类,这在大型代码库中是有风险的。

拿不准时,先从组合开始。如果之后真的出现了 “is-a” 层次结构,你随时可以重构为继承。反向操作——把深层的继承树拆解成组合——则要困难得多。

5. 实战示例:通知系统

我们把继承应用到完全不同的领域,以说明这些模式并不只局限于车辆。想象你正在构建一个通知系统,可以通过不同渠道发送消息:邮件、短信和推送通知。

所有通知类型都共享一些通用属性:recipient(接收者)、message(消息内容)和 timestamp(时间戳)。它们都需要一个 formatHeader() 方法来生成统一的头部格式。但 send() 方法在每个渠道中各不相同:邮件需要主题行,短信有字数限制,推送通知则有设备令牌和优先级。

这个设计为什么有效
  • 共享逻辑只写一次。 recipientmessagetimestamp 字段都定义在 Notification 中。formatHeader() 方法被三种通知类型共同继承,从而在邮件、短信和推送之间产生一致的头部格式。如果你想更改时间戳格式,只需改一个方法。
  • 每个子类都封装了渠道特有的复杂性。 SMSNotification 处理 160 字的限制,PushNotification 管理设备令牌和优先级,EmailNotification 添加主题行。这些细节都不会泄漏到父类或彼此之间。
  • 新增渠道很简单。 需要 Slack 通知?创建一个 SlackNotification extends Notification,添加 webhookUrl 字段,重写 send()。无需改动任何现有代码。

下面给出通知系统的完整 Java 实现。

通知系统(Java)
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

class Notification {
    protected String recipient;
    protected String message;
    protected String timestamp;

    public Notification(String recipient, String message) {
        this.recipient = recipient;
        this.message = message;
        this.timestamp = LocalDateTime.now()
            .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

    public String formatHeader() {
        return "[" + timestamp + "] To: " + recipient;
    }

    public void send() {
        System.out.println(formatHeader());
        System.out.println("Message: " + message);
    }
}

class EmailNotification extends Notification {
    private String subject;

    public EmailNotification(String recipient, String message, String subject) {
        super(recipient, message);
        this.subject = subject;
    }

    @Override
    public void send() {
        System.out.println(formatHeader());
        System.out.println("Subject: " + subject);
        System.out.println("Body: " + message);
        System.out.println("Status: Email delivered");
    }
}

class SMSNotification extends Notification {
    private String phoneNumber;
    private static final int MAX_LENGTH = 160;

    public SMSNotification(String recipient, String message, String phoneNumber) {
        super(recipient, message);
        this.phoneNumber = phoneNumber;
    }

    @Override
    public void send() {
        System.out.println(formatHeader());
        System.out.println("Phone: " + phoneNumber);
        String smsBody = message.length() > MAX_LENGTH
            ? message.substring(0, MAX_LENGTH - 3) + "..."
            : message;
        System.out.println("SMS: " + smsBody);
        System.out.println("Status: SMS sent (" + smsBody.length() + "/" + MAX_LENGTH + " chars)");
    }
}

class PushNotification extends Notification {
    private String deviceToken;
    private String priority;

    public PushNotification(String recipient, String message,
                            String deviceToken, String priority) {
        super(recipient, message);
        this.deviceToken = deviceToken;
        this.priority = priority;
    }

    @Override
    public void send() {
        System.out.println(formatHeader());
        System.out.println("Device: " + deviceToken.substring(0, 8) + "...");
        System.out.println("Priority: " + priority);
        System.out.println("Alert: " + message);
        System.out.println("Status: Push notification delivered");
    }
}

public class Main {
    public static void main(String[] args) {
        EmailNotification email = new EmailNotification(
            "alice@example.com", "Your order has been shipped!", "Order Update");
        email.send();

        System.out.println();

        SMSNotification sms = new SMSNotification(
            "Bob", "Your verification code is 482910.", "+1-555-0123");
        sms.send();

        System.out.println();

        PushNotification push = new PushNotification(
            "Charlie", "New message from Alice",
            "d8a3f4b2c1e5a9b7", "high");
        push.send();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值