接口与抽象类特性分析
- 接口特性
- 只定义方法签名,不包含方法实现。
- 可以用于定义对象的形状,一个类可以实现多个接口。
- 主要用于对行为的抽象,不涉及具体的实现细节。
- 抽象类特性
- 可以包含抽象方法和具体方法。抽象方法只有声明,没有实现;具体方法有方法体。
- 一个类只能继承一个抽象类。
- 抽象类可以包含成员变量,用于存储与该抽象类型相关的数据。
不同模块场景下的选择
- 当需要定义行为契约,且不关心具体实现时,优先选择接口
- 场景:在企业级项目中,不同模块可能需要与外部服务交互,例如支付模块、消息推送模块等。这些模块只需要遵循特定的接口来定义请求和响应的格式,而具体的实现可以由不同的供应商提供。
- 优势:可维护性高,不同模块实现接口互不干扰;可扩展性强,新的实现可以轻松添加;对性能影响小,因为接口只定义契约。
- 示例:
// 定义支付接口
interface Payment {
pay(amount: number): void;
}
// 支付宝支付实现
class AlipayPayment implements Payment {
pay(amount: number) {
console.log(`使用支付宝支付 ${amount} 元`);
}
}
// 微信支付实现
class WechatPayment implements Payment {
pay(amount: number) {
console.log(`使用微信支付 ${amount} 元`);
}
}
- 当存在部分通用实现,且需要建立类型层次结构时,选择抽象类
- 场景:在项目的用户管理模块中,可能有普通用户、管理员用户等不同类型,它们有一些共同的属性和方法,如用户名、登录方法等,但也有各自独特的行为。
- 优势:可维护性方面,通用代码放在抽象类中,一处修改处处生效;可扩展性上,通过继承抽象类可以方便地添加新的用户类型;性能上,抽象类的方法调用相对直接,减少了中间查找接口实现的开销。
- 示例:
// 抽象用户类
abstract class User {
constructor(public username: string) {}
abstract login(): void;
logout() {
console.log(`${this.username} 已退出登录`);
}
}
// 普通用户类
class NormalUser extends User {
login() {
console.log(`${this.username} 普通用户登录`);
}
}
// 管理员用户类
class AdminUser extends User {
login() {
console.log(`${this.username} 管理员用户登录`);
}
}
可能遇到的坑及解决方案
- 接口方面
- 坑:接口不能包含实现,当多个实现类有大量重复代码时,会导致代码冗余。
- 解决方案:可以将重复代码提取到一个工具函数或类中,由实现类调用。或者使用混入(mixin)的方式,将重复逻辑混入到实现类中。
- 抽象类方面
- 坑:由于一个类只能继承一个抽象类,在需要多重继承特性时会受限。
- 解决方案:可以通过接口来实现类似多重继承的效果,即一个类继承抽象类的同时实现多个接口。同时,也可以考虑使用组合的方式,将不同的功能组合到一个类中,而不是依赖继承。