TypeScript 抽象类与抽象方法
·
本文献给:
已掌握 TypeScript 类基础、继承、多态、方法重写等知识的开发者。本文将带你学习抽象类(abstract)与抽象方法的定义和使用场景,以及抽象类与接口的区别,帮助你设计更合理的继承结构。
你将学到:
- 抽象类的定义与特点
- 抽象方法的语法与强制实现规则
- 抽象类不能实例化的原因
- 抽象类与接口的对比与选择
- 实际场景中的应用示例
目录
一、抽象类(Abstract Class)
抽象类使用 abstract 关键字定义,用于作为其他类的基类。它不能直接被实例化,只能被子类继承。
abstract class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
move(): void {
console.log(`${this.name} is moving`);
}
}
// const a = new Animal("Creature"); // ❌ 不能实例化抽象类
class Dog extends Animal {
bark() {
console.log(`${this.name} barks`);
}
}
const dog = new Dog("Buddy");
dog.move(); // "Buddy is moving"
dog.bark(); // "Buddy barks"
抽象类可以包含具体实现的方法(如上面的 move),也可以包含抽象方法。
二、抽象方法(Abstract Method)
抽象方法在抽象类中声明,但没有方法体。子类必须实现抽象方法(除非子类也是抽象类)。
abstract class Shape {
abstract getArea(): number; // 抽象方法,没有实现
describe(): string {
return `Area: ${this.getArea()}`;
}
}
class Circle extends Shape {
radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
// 必须实现 getArea
getArea(): number {
return Math.PI * this.radius ** 2;
}
}
class Square extends Shape {
side: number;
constructor(side: number) {
super();
this.side = side;
}
getArea(): number {
return this.side ** 2;
}
}
const shapes: Shape[] = [new Circle(5), new Square(4)];
shapes.forEach(s => console.log(s.describe()));
2.1 抽象方法的访问修饰符
抽象方法可以用 protected 或 public 修饰(默认 public)。子类实现时不能缩小访问权限。
abstract class Base {
protected abstract init(): void;
}
class Derived extends Base {
// 实现时可以是 public 或 protected,但不能是 private
protected init() {
console.log("initialized");
}
}
2.2 抽象方法不能有实现
抽象方法不能包含方法体,甚至连空花括号都不行。
abstract class Demo {
// abstract method() {} // ❌ 不能有实现
abstract method(): void; // ✅
}
三、抽象类的特点与约束
3.1 可以包含构造方法
抽象类可以有构造函数,用于初始化公共字段,供子类通过 super 调用。
abstract class Person {
constructor(public name: string, public age: number) {}
abstract introduce(): void;
}
class Student extends Person {
constructor(name: string, age: number, public grade: number) {
super(name, age);
}
introduce() {
console.log(`I'm ${this.name}, ${this.age} years old, grade ${this.grade}`);
}
}
3.2 可以包含具体方法
抽象类中既有抽象方法,也有具体实现的方法,这是抽象类与接口的最大区别之一。
3.3 子类必须实现所有抽象方法
如果子类没有实现所有抽象方法,该子类也必须声明为 abstract。
abstract class Animal {
abstract eat(): void;
abstract sleep(): void;
}
class Dog extends Animal {
eat() { console.log("Dog eats"); }
// 缺少 sleep 方法 ❌
}
// 正确:子类也是抽象类
abstract class Mammal extends Animal {
eat() { console.log("Mammal eats"); }
// sleep 仍然抽象
}
四、抽象类 vs 接口
两者经常被拿来比较,因为都用于定义契约。但适用场景不同。
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 实例化 | 不能 | 不能 |
| 方法实现 | 可以有具体方法 | 不能有实现(TS 接口只有声明) |
| 字段(状态) | 可以有实例字段 | 只能声明,不能初始化 |
| 构造函数 | 可以有 | 不能有 |
| 访问修饰符 | 支持 public/protected/private |
所有成员隐含 public |
| 多继承 | 一个类只能继承一个抽象类 | 一个类可以实现多个接口 |
| 适用场景 | 共享代码、状态、部分实现 | 描述能力/契约,轻量无状态 |
4.1 何时使用抽象类
- 多个类有共同的状态(字段)和行为实现,但部分行为需要子类差异化实现。
- 需要提供部分默认实现,减少子类重复代码。
- 需要构造函数或
protected成员。
4.2 何时使用接口
- 只需要声明“能做什么”,不关心“怎么做”。
- 一个类需要符合多个契约(多接口实现)。
- 类型需要跨不同类层次结构(例如
Comparable、Serializable)。 - 为现有类补充类型定义(声明文件)。
4.3 示例:同时使用抽象类和接口
interface Flyable {
fly(): void;
}
interface Swimmable {
swim(): void;
}
abstract Animal {
constructor(public name: string) {}
abstract makeSound(): void;
}
class Duck extends Animal implements Flyable, Swimmable {
makeSound() { console.log("Quack"); }
fly() { console.log(`${this.name} flies`); }
swim() { console.log(`${this.name} swims`); }
}
五、常见错误与注意事项
5.1 直接实例化抽象类
abstract class Base {}
// new Base(); // ❌ 无法创建抽象类的实例
5.2 子类未实现抽象方法
abstract class A {
abstract test(): void;
}
class B extends A {} // ❌ 非抽象类 B 未实现 test
5.3 抽象方法使用 private
abstract class C {
private abstract method(): void; // ❌ 抽象方法不能是 private
}
因为抽象方法需要在子类中实现,private 无法被子类访问。
5.4 抽象类中过度使用具体方法
抽象类如果大部分方法都是具体的,可以考虑是否应该设计为普通基类。抽象类的核心价值在于定义“不完整”的模板。
5.5 抽象类与构造函数中的抽象方法调用
在抽象类的构造函数中调用抽象方法是安全的,但要确保子类实现的方法不依赖于子类尚未初始化的字段。
abstract class Base {
constructor() {
this.init(); // 调用抽象方法
}
protected abstract init(): void;
}
class Derived extends Base {
private data: string = "";
protected init() {
console.log(this.data); // 此时 data 还是默认值 "",不是问题
}
}
六、综合示例
// 抽象类:设备
abstract class Device {
constructor(public brand: string, public model: string) {}
// 具体方法
turnOn(): void {
console.log(`${this.brand} ${this.model} is turning on`);
}
turnOff(): void {
console.log(`${this.brand} ${this.model} is turning off`);
}
// 抽象方法
abstract performFunction(): void;
}
// 子类:手机
class Phone extends Device {
constructor(brand: string, model: string, private os: string) {
super(brand, model);
}
performFunction() {
console.log(`Making a call from ${this.brand} ${this.model} (${this.os})`);
}
installApp(appName: string) {
console.log(`Installing ${appName}`);
}
}
// 子类:打印机
class Printer extends Device {
constructor(brand: string, model: string, private pagesPerMinute: number) {
super(brand, model);
}
performFunction() {
console.log(`Printing document at ${this.pagesPerMinute} ppm`);
}
loadPaper(amount: number) {
console.log(`Loaded ${amount} sheets`);
}
}
// 使用多态
function operateDevice(device: Device) {
device.turnOn();
device.performFunction();
device.turnOff();
}
const phone = new Phone("Apple", "iPhone 15", "iOS");
const printer = new Printer("HP", "LaserJet", 30);
operateDevice(phone);
operateDevice(printer);
// 检查类型
if (phone instanceof Phone) {
phone.installApp("WhatsApp");
}
if (printer instanceof Printer) {
printer.loadPaper(500);
}
// 抽象类作为数组元素类型
const devices: Device[] = [phone, printer];
devices.forEach(d => d.performFunction());
七、小结
| 概念 | 语法/示例 | 说明 |
|---|---|---|
| 抽象类 | abstract class A {} |
不能实例化,作为基类 |
| 抽象方法 | abstract method(): void; |
无方法体,子类必须实现 |
| 具体方法 | 抽象类中可以包含普通方法 | 提供默认实现 |
| 构造函数 | 抽象类可以有构造函数,供子类调用 | 用于初始化公共字段 |
| vs 接口 | 抽象类有状态和部分实现,接口纯契约 | 按需选择 |
| 子类未实现抽象方法 | 子类必须也标记为 abstract |
否则编译错误 |
觉得文章有帮助?别忘了:
👍 点赞 👍 – 给我一点鼓励
⭐ 收藏 ⭐ – 方便以后查看
🔔 关注 🔔 – 获取更新通知
标签: #TypeScript #抽象类 #抽象方法 #面向对象 #学习笔记 #前端开发
更多推荐
所有评论(0)