TypeScript 抽象类详解及应用实例(十七)
- 编程语言
- 5天前
- 7热度
- 0评论
在 TypeScript 面向对象编程中,抽象类(Abstract Class)是一个重要的概念。本文将详细介绍抽象类的用途、基本语法、实例应用以及与其他类型(如接口)的区别,并通过具体的示例帮助你更好地理解和使用抽象类。
为什么需要抽象类
在面向对象编程中,我们经常需要定义一些基类,这些基类描述了子类共同的属性和方法。然而,有些方法的具体实现细节需要由子类来决定。抽象类正是为了解决这一问题而设计的。它允许我们在基类中定义方法的签名(声明),而将具体实现交给子类。
抽象类的特点
- 不能直接实例化:抽象类不能直接使用 new 关键字创建实例,只能作为基类供其他类继承。
- 定义公共结构:抽象类主要用于定义子类的公共属性和方法,为子类提供一个统一的结构和行为模板。
- 包含抽象方法:抽象类可以包含抽象方法(只有声明,没有实现),子类必须实现这些抽象方法。
抽象类基础
使用 abstract 关键字声明抽象类和抽象方法。抽象类可以包含抽象方法和具象方法(已有实现的方法)。
示例
// 定义抽象类 Animal
abstract class Animal {
// 抽象方法:子类必须实现
abstract speak(): void;
// 具象方法:子类可以直接使用
move() {
console.log('在移动');
}
}
// 定义 Dog 类继承 Animal
class Dog extends Animal {
speak() {
console.log('汪汪汪!');
}
}
// 创建 Dog 实例
const dog = new Dog();
dog.speak(); // 输出: 汪汪汪!
dog.move(); // 输出: 在移动抽象方法
抽象方法是抽象类中只声明但不实现的方法。子类继承抽象类后,必须实现所有抽象方法,否则会编译报错。
示例
// 定义抽象类 Shape
abstract class Shape {
// 抽象方法:子类必须实现
abstract area(): number;
abstract perimeter(): number;
// 具象方法:可以调用抽象方法
describe() {
console.log(`面积: ${this.area().toFixed(2)}`);
}
}
// 定义 Rectangle 类继承 Shape
class Rectangle extends Shape {
width: number;
height: number;
constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
perimeter() {
return 2 * (this.width + this.height);
}
}
// 创建 Rectangle 实例
const rect = new Rectangle(4, 5);
rect.describe(); // 输出: 面积: 20.00
console.log(`周长: ${rect.perimeter()}`); // 输出: 周长: 18抽象类作为类型
抽象类可以作为参数类型使用,这意味着函数可以接受任何抽象类的子类实例,从而实现多态。
示例
// 定义抽象类 Animal
abstract class Animal {
// 抽象方法:所有动物都会发出声音
abstract speak(): void;
}
// 定义 Cat 类继承 Animal
class Cat extends Animal {
speak() {
console.log('喵喵喵!');
}
}
// 定义 Dog 类继承 Animal
class Dog extends Animal {
speak() {
console.log('汪汪汪!');
}
}
// 定义函数参数类型为抽象类 Animal
function makeSpeak(animal: Animal) {
animal.speak();
}
// 传入不同的子类实例,实现不同的行为(多态)
makeSpeak(new Cat()); // 输出: 喵喵喵!
makeSpeak(new Dog()); // 输出: 汪汪汪!
// 抽象类类型数组:可以存储不同子类实例
const animals: Animal[] = [new Cat(), new Dog()];抽象类与接口的区别
抽象类和接口都用于定义类型规范,但它们有一些关键区别。理解这些区别有助于在实际开发中做出正确的选择。
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 实例化 | 不能直接实例化 | 不能直接实例化 |
| 方法实现 | 可以有具体实现 | 只能有声明(TypeScript 3.6+ 可有默认实现) |
| 成员修饰符 | 可以添加 public、protected、private | 只能有 readonly |
| 继承 | 单继承(只能 extends 一个类) | 多实现(可以 implements 多个接口) |
| 构造函数 | 可以有构造函数 | 不能有构造函数 |
选择建议
- 需要共享代码:使用抽象类。
- 需要定义规范/契约:使用接口。
- 需要多继承:使用接口。
完整示例:支付系统
下面是一个使用抽象类实现的支付系统示例,展示了抽象类在实际项目中的应用。
示例
// 定义抽象支付类
abstract class Payment {
// 抽象方法:处理支付,子类必须实现
abstract process(amount: number): boolean;
// 具象方法:验证支付信息
validate() {
console.log('验证支付信息');
}
}
// 信用卡支付类
class CreditCardPayment extends Payment {
cardNumber: string;
constructor(cardNumber: string) {
super();
this.cardNumber = cardNumber;
}
process(amount: number): boolean {
console.log(`处理信用卡支付: ${amount}`);
return true;
}
}
// PayPal 支付类
class PayPalPayment extends Payment {
email: string;
constructor(email: string) {
super();
this.email = email;
}
process(amount: number): boolean {
console.log(`处理 PayPal 支付: ${amount}`);
return true;
}
}
// 使用多态处理不同的支付方式
const payments: Payment[] = [
new CreditCardPayment('1234'),
new PayPalPayment('test@example.com')
];
// 遍历处理每种支付方式
for (const payment of payments) {
payment.validate();
payment.process(100);
}运行结果
验证支付信息
处理信用卡支付: 100
验证支付信息
处理 PayPal 支付: 100应用场景
抽象类常用于框架设计和业务逻辑分层。例如支付处理、用户认证、数据持久化等场景。
注意事项
- 不能实例化:抽象类不能直接使用 new 创建实例,必须通过子类继承。
- 必须实现抽象方法:子类如果不实现所有抽象方法,会编译报错。
- 可以有构造函数:抽象类可以定义构造函数,子类需要调用 super()。
- 单继承:一个类只能继承一个抽象类(单继承)。
- 访问修饰符:抽象方法可以使用 public、protected 修饰符。
最佳实践
当多个类需要共享代码和逻辑时,使用抽象类。当只需要定义规范和契约时,使用接口。
总结
抽象类是 TypeScript 面向对象编程的重要组成部分。通过使用 abstract 关键字,我们可以声明抽象类和方法,确保子类实现特定的行为。抽象类不仅提供了代码复用的机制,还支持多态,使得代码更加灵活和可扩展。
- abstract 关键字:用于声明抽象类和方法。
- 不能实例化:只能通过子类继承使用。
- 抽象方法:子类必须实现的方法。
- 具象方法:子类可以直接继承使用的有实现的方法。
- 模板方法模式:抽象类定义骨架,子类提供具体实现。
在设计类层次结构时,优先考虑使用抽象类来共享代码和定义规范。希望本文能帮助你更好地理解和使用 TypeScript 中的抽象类。