继承(Inheritance)
继承允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和行为。
简单来说:
继承通过让你在基类中只定义一次通用逻辑,然后在多个派生类中扩展或特化它,从而实现代码复用。
这让软件更整洁、模块化,也更易于维护。
现实世界类比
想象一个 Web 应用中的 User(用户)系统:
- 基类
User持有通用属性,如username、email,以及方法如login()或logout()。 - 像
Admin、Customer、Vendor这样的专属角色继承自User,但会添加各自角色特有的行为。
所有专属用户类型都从 User 类继承通用数据和行为,同时可以扩展功能以适应各自的角色。
1. 为什么继承很重要
继承带来了多项好处,使其成为面向对象编程中一个强大的设计工具。
1. 代码复用性
它体现了 DRY(Don’t Repeat Yourself,不要重复自己) 原则。通用逻辑在父类中只写一次,就能在所有子类中共享,从而减少冗余。
2. 清晰的逻辑层次
它建立了清晰直观的层次结构,用于建模现实世界中的 “is-a”(是一种) 关系,例如 ElectricCar 是一种 Car,或 Admin 是一种 User。
3. 易于维护
如果在共享逻辑中发现 bug 或需要改动,你只需在一个地方——即父类——修复它。所有子类都会自动继承这个修复。
4. 多态性
继承是多态性的前提条件,它允许将不同子类的对象当作父类的对象来对待。
2. 继承是如何运作的
当一个类继承自另一个类时:
- 子类继承父类的所有非私有字段和方法。
- 子类可以重写(override)继承来的方法,以提供不同的实现。
- 子类还可以通过添加新字段和新方法来扩展父类。
这同时实现了复用与定制。
示例
继承最基础的形式是:一个子类扩展父类,并在继承来的字段和方法之上添加新行为。下面是用继承构建的车辆层次结构。
这个 Vehicle 类定义了所有车辆共享的基本属性和通用行为。
接下来可以创建专门的车辆类型:
在这个例子中:
ElectricCar和GasCar都从Vehicle类继承了make、model、startEngine()和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 层以上)会变得脆弱且难以理解。
分层继承是指多个子类扩展同一个父类。我们前面的车辆例子中,ElectricCar 和 GasCar 都扩展 Vehicle,这就是分层继承。这非常常见,也完全自然。
多重继承是指一个子类扩展多个父类。这里事情就开始变复杂了。只有 C++ 和 Python 直接支持多重继承。Java、C# 和 TypeScript 不支持。原因是什么?菱形继承问题(diamond problem)。
想象 ElectricCar 同时扩展 Vehicle 和 Machine。Vehicle 和 Machine 都有一个 start() 方法。当你调用 electricCar.start() 时,到底执行哪个版本?来自 Vehicle 的?来自 Machine 的?还是两者都执行?
C++ 用虚继承(virtual inheritance)来处理,这种方式复杂且容易出错。Python 则采用方法解析顺序(MRO),一种明确界定的算法(C3 线性化)来决定哪个父类的方法优先。Java 和 C# 完全绕开了这个问题——它们只允许单类继承,你可以实现多个接口,但只能扩展一个类。
4. 何时使用继承
继承很强大,但不应滥用,只有在它真正能建模现实世界的关系时才使用。如果在设计早期做出错误判断,会导致代码难以修改、难以测试、也难以理解。
下面是一份实用的检查清单。
适合使用继承的情况:
- 存在清晰的 “is-a”(是一种)关系(例如
Dog is an Animal、Car is a Vehicle)。如果你无法自然地说出"X 是一种 Y",那继承可能就是错误的工具,这类关系应该用组合。 - 父类定义了子类应该共享的通用行为或数据。例如,所有车辆都有
startEngine()方法,把它放在父类中就能避免在每个车辆类型里重复编写。 - 子类不破坏父类预期的行为。如果有人持有一个指向
ElectricCar的Vehicle引用,那么每个Vehicle方法都应当如预期般工作。 - 你希望通过共享逻辑和结构来促进代码复用,且层次结构较浅(最多 2-3 层)。
应避免继承的情况:
- 关系是 “has-a”(有一个)或 “uses-a”(使用一个),而非 “is-a”。
Car拥有一个Engine,但它不是Engine。Printer使用一个Logger,但它不是Logger。 - 你希望动态地组合来自多个来源的行为。继承在编译期就把你锁定在单一父类上,而组合让你可以自由地混搭各个组件。
- 你需要运行时的灵活性来替换行为。使用组合,你可以注入不同的实现(把
FileLogger换成ConsoleLogger)。而继承中,父子关系是固定的。 - 你希望避免子类与父类内部之间的紧耦合。对父类的改动会向下波及层次结构中的每一个子类,这在大型代码库中是有风险的。
拿不准时,先从组合开始。如果之后真的出现了 “is-a” 层次结构,你随时可以重构为继承。反向操作——把深层的继承树拆解成组合——则要困难得多。
5. 实战示例:通知系统
我们把继承应用到完全不同的领域,以说明这些模式并不只局限于车辆。想象你正在构建一个通知系统,可以通过不同渠道发送消息:邮件、短信和推送通知。
所有通知类型都共享一些通用属性:recipient(接收者)、message(消息内容)和 timestamp(时间戳)。它们都需要一个 formatHeader() 方法来生成统一的头部格式。但 send() 方法在每个渠道中各不相同:邮件需要主题行,短信有字数限制,推送通知则有设备令牌和优先级。
这个设计为什么有效
- 共享逻辑只写一次。
recipient、message和timestamp字段都定义在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();
}
}
&spm=1001.2101.3001.5002&articleId=163982299&d=1&t=3&u=9a772522bb3345a5a01e8ebe2b08aa34)
937

被折叠的 条评论
为什么被折叠?



