AI Library · 知识库

# 继承与重写:类层次与方法覆盖

学习目标:在框架组件定制场景下理解类继承、虚方法分派与方法重写(Override),并明确无类继承语言的替代设计。

前置知识与范围:类与方法。聚焦经典面向对象中的模板方法模式(Template Method Pattern)与虚方法覆盖。

# 问题与边界

在许多应用框架(如生命周期管理、测试基类、中间件管道)中,框架提供抽象基类 BaseWorker,要求用户派生定制:

# 关键差异

语言 继承语法与关键字 重写防护机制 默认方法虚性(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)、菱形继承冲突以及状态与行为隐性耦合,因此在语言层面完全抛弃了类的实现继承:

# 迁移误区与适用限制

# 运行与验证

语言版本基线见示例基线。 所有程序运行后控制台输出均为生命周期三段记录:

纯文本log: [start, exporting data, finish]

模板方法的调用时序与重写覆盖断言校验全部通过。

# 相关阅读