如何在TypeScript中遵循里氏替换原则?重写签名问题解析
问题背景
子类重写父类方法时,如果缩小参数类型范围,会违反里氏替换原则——即父类的实例可以被子类实例替换,且程序行为保持一致。但在TypeScript中,以下代码在编译阶段不会报错,只有实际调用跨类型比较时才会触发错误:
abstract class Person { public abstract compare(that: Person): boolean; } class Student extends Person { constructor(private readonly maxGrade: number) { super(); } public override compare(that: Student) { return this.maxGrade > that.maxGrade; } } class Lawyer extends Person { constructor(private readonly yearlyIncome: number){ super(); } public override compare(that: Lawyer) { return this.yearlyIncome < that.yearlyIncome; } } // 取消注释后才会报错:类型“Lawyer”的参数不能赋给类型“Student”的参数 // const s = new Student(96); // const soul = new Lawyer(7400); // console.log(s.compare(soul));
对比之下,Python的类型检查工具(如mypy)会直接在编译阶段识别这种违反原则的重写并报错。
解决方案
要让TypeScript代码符合里氏替换原则,同时在编译阶段提前检测问题,可采用以下几种方式:
1. 严格兼容父类方法签名
子类重写的方法必须接受父类方法定义的所有参数类型,同时针对自身类型做逻辑处理:
abstract class Person { public abstract compare(that: Person): boolean; } class Student extends Person { constructor(private readonly maxGrade: number) { super(); } public override compare(that: Person): boolean { // 仅处理同类型实例,其他情况返回合理默认值 if (!(that instanceof Student)) { return false; // 或根据业务需求抛出错误 } return this.maxGrade > that.maxGrade; } } class Lawyer extends Person { constructor(private readonly yearlyIncome: number){ super(); } public override compare(that: Person): boolean { if (!(that instanceof Lawyer)) { return false; } return this.yearlyIncome < that.yearlyIncome; } } // 跨类型比较返回默认值,编译阶段无错误 const s = new Student(96); const soul = new Lawyer(7400); console.log(s.compare(soul)); // false
这种方式严格遵循里氏替换原则:任何Person类型的实例都可以传入compare方法,子类会正确处理所有合法输入。
2. 泛型约束实现同类型安全比较
如果业务逻辑仅允许同类型实例比较,可通过泛型绑定父类与子类的类型关系,在编译阶段强制约束:
abstract class Person<T extends Person<T>> { public abstract compare(that: T): boolean; } class Student extends Person<Student> { constructor(private readonly maxGrade: number) { super(); } public override compare(that: Student): boolean { return this.maxGrade > that.maxGrade; } } class Lawyer extends Person<Lawyer> { constructor(private readonly yearlyIncome: number){ super(); } public override compare(that: Lawyer): boolean { return this.yearlyIncome < that.yearlyIncome; } } // 编译阶段直接报错:类型“Lawyer”的参数不能赋给类型“Student”的参数 const s = new Student(96); const soul = new Lawyer(7400); console.log(s.compare(soul));
泛型约束让父类的compare方法仅接受子类自身类型的实例,既保证了类型安全,也符合里氏替换原则——当Student作为Person<Student>使用时,它完全满足父类的契约。
3. 开启严格函数类型检查
TypeScript默认的类型检查对方法重写的参数兼容性要求较宽松,开启strictFunctionTypes编译选项后,会严格检测参数类型的兼容性,直接在编译阶段发现违反里氏替换原则的重写:
在tsconfig.json中配置:
{ "compilerOptions": { "strictFunctionTypes": true } }
此时原始代码会直接触发编译错误:
Property 'compare' in type 'Student' is not assignable to the same property in base type 'Person'.
Type '(that: Student) => boolean' is not assignable to type '(that: Person) => boolean'.
Types of parameters 'that' and 'that' are incompatible.
Type 'Person' is not assignable to type 'Student'.
这种方式无需修改代码,仅通过配置就能提前发现问题。
内容的提问来源于stack exchange,提问作者OrenIshShalom

