# 继承与重写:类层次与方法覆盖
学习目标:在框架组件定制场景下理解类继承、虚方法分派与方法重写(Override),并明确无类继承语言的替代设计。
前置知识与范围:类与方法。聚焦经典面向对象中的模板方法模式(Template Method Pattern)与虚方法覆盖。
# 问题与边界
在许多应用框架(如生命周期管理、测试基类、中间件管道)中,框架提供抽象基类 BaseWorker,要求用户派生定制:
- 基类契约:
- 提供公开的生命周期骨架方法
run()(模板方法)。 - 内部依次执行:记录
"start"、调用待子类重写的钩子方法do_work()、记录"finish"。
- 提供公开的生命周期骨架方法
- 派生类
ExportWorker: 继承BaseWorker,并重写do_work(),在其中执行具体的导出工作并记录"exporting data"。 - 执行结果预期:按顺序输出/记录:
"start""exporting data""finish"
# 关键差异
| 语言 | 继承语法与关键字 | 重写防护机制 | 默认方法虚性(Virtual) |
|---|---|---|---|
| Java | class Sub extends Super |
@Override 注解(编译期拼写检查) |
默认全部为虚方法,除非声明为 final |
| Kotlin | class Sub : Super() |
override 强制关键字 |
默认全部为 final,基类与方法必须显式标明 open |
| Python | class Sub(Super): |
无原生重写语法(可用 @override 提示) |
默认全部动态分派 |
| C++ | class Sub : public Super |
override 说明符(C++11,强力推荐) |
默认非虚,基类必须显式声明 virtual |
| JS / TS | class Sub extends Super |
TS 原生支持 override 关键字修饰符 |
默认全部在原型链上动态查找 |
| Go / Rust | 原生不支持实现继承 | Go 用结构体内嵌;Rust 用 Trait 泛型 | 无虚方法表,采用接口或 Trait 分派 |
# 自然适用语言实现
# Java
Javaimport java.util.ArrayList;
import java.util.List;
public class Main {
public static abstract class BaseWorker {
public final List<String> log = new ArrayList<>();
public void run() {
log.add("start");
doWork();
log.add("finish");
}
protected abstract void doWork();
}
public static class ExportWorker extends BaseWorker {
@Override
protected void doWork() {
log.add("exporting data");
}
}
public static void main(String[] args) {
BaseWorker worker = new ExportWorker();
worker.run();
assert worker.log.size() == 3;
assert "start".equals(worker.log.get(0));
assert "exporting data".equals(worker.log.get(1));
assert "finish".equals(worker.log.get(2));
System.out.println("log: " + worker.log);
}
}
Java 中推荐对模板方法 run() 标记 final 防止子类破坏执行骨架,@Override 确保重写签名一致。
# Kotlin
Kotlinabstract class BaseWorker {
val log = mutableListOf<String>()
fun run() {
log.add("start")
doWork()
log.add("finish")
}
protected abstract fun doWork()
}
class ExportWorker : BaseWorker() {
// Kotlin 强制要求 override 关键字
override fun doWork() {
log.add("exporting data")
}
}
fun main() {
val worker: BaseWorker = ExportWorker()
worker.run()
check(worker.log == listOf("start", "exporting data", "finish"))
println("log: ${worker.log}")
}
Kotlin 崇尚“闭合默认”(Effective Java 条款):类与方法默认都是不可继承的 final,唯有明确设计的扩展点才允许重写。
# Python
Pythonfrom abc import ABC, abstractmethod
from typing import List
class BaseWorker(ABC):
def __init__(self) -> None:
self.log: List[str] = []
def run(self) -> None:
self.log.append("start")
self.do_work()
self.log.append("finish")
@abstractmethod
def do_work(self) -> None:
pass
class ExportWorker(BaseWorker):
def do_work(self) -> None:
self.log.append("exporting data")
if __name__ == "__main__":
worker: BaseWorker = ExportWorker()
worker.run()
assert worker.log == ["start", "exporting data", "finish"]
print(f"log: {worker.log}")
Python 使用标准库 abc.ABC 与 @abstractmethod 在实例化时检查抽象方法是否被全部覆盖。
# C++
C++#include <iostream>
#include <vector>
#include <string>
#include <cassert>
class BaseWorker {
public:
std::vector<std::string> log;
virtual ~BaseWorker() = default; // 基类必须有虚析构函数
void run() {
log.push_back("start");
do_work();
log.push_back("finish");
}
protected:
virtual void do_work() = 0; // 纯虚函数
};
class ExportWorker : public BaseWorker {
protected:
void do_work() override { // C++11 override 守护
log.push_back("exporting data");
}
};
int main() {
ExportWorker worker;
worker.run();
assert(worker.log.size() == 3);
assert(worker.log[0] == "start");
assert(worker.log[1] == "exporting data");
assert(worker.log[2] == "finish");
std::cout << "log: [" << worker.log[0] << ", " << worker.log[1] << ", " << worker.log[2] << "]\n";
}
C++ 基类必须提供 virtual ~BaseWorker() = default;,否则通过基类指针销毁派生类对象将触发未定义行为(内存泄漏或崩溃)。
# TypeScript / JavaScript
TypeScriptconst assert = {
equal: (a: unknown, b: unknown) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
deepEqual: (a: unknown, b: unknown) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
throws: (fn: () => void, errType?: unknown) => {
try { fn(); throw new Error("expected throw"); } catch (e: unknown) {
if (e instanceof Error && e.message === "expected throw") throw e;
if (typeof errType === "function" && !(e instanceof errType)) throw e;
}
}
};
abstract class BaseWorker {
readonly log: string[] = [];
run(): void {
this.log.push("start");
this.doWork();
this.log.push("finish");
}
protected abstract doWork(): void;
}
class ExportWorker extends BaseWorker {
override doWork(): void {
this.log.push("exporting data");
}
}
const worker: BaseWorker = new ExportWorker();
worker.run();
assert.deepEqual(worker.log, ["start", "exporting data", "finish"]);
console.log(`log: [${worker.log.join(", ")}]`);
TypeScript 在启用 noImplicitOverride 配置时,强制显式书写 override 关键字。
# 无类继承语言的取舍
Go 和 Rust 的设计者认为:深层次类继承容易导致脆弱基类(Fragile Base Class)、菱形继承冲突以及状态与行为隐性耦合,因此在语言层面完全抛弃了类的实现继承:
- Go 的替代:结构体内嵌组合与函数回调
Go 中内嵌结构体只是将字段和方法提升(Promotion),并不构成面向对象的多态(Polymorphism)。基类方法中调用同名方法时,无法动态分派到外部结构体上。因此在 Go 中实现模板方法时,通常将钩子方法作为函数字段显式注入:
Go
type Worker struct { Log []string DoWork func() } func (w *Worker) Run() { w.Log = append(w.Log, "start") w.DoWork() w.Log = append(w.Log, "finish") } - Rust 的替代:Trait 配合默认方法实现
Rust 使用 Trait 约定接口,并可以在 Trait 内提供默认实现:
Rust
trait Worker { fn do_work(&mut self); fn run(&mut self) { // 骨架逻辑 self.do_work(); } }
# 迁移误区与适用限制
- 脆弱基类问题:基类改动一个看似无害的方法实现,可能会打乱深层派生类预设的时序或状态假定,导致级联损坏。继承层次一般不宜超过 2~3 层。
- C++ 缺失虚析构函数:派生类如果包含动态分配资源,而基类析构函数未声明为
virtual,通过基类指针delete时派生类的析构函数根本不会被执行。
# 运行与验证
语言版本基线见示例基线。 所有程序运行后控制台输出均为生命周期三段记录:
纯文本log: [start, exporting data, finish]
模板方法的调用时序与重写覆盖断言校验全部通过。