name: solid-principles description: SOLID面向对象设计原则,用于构建可维护的代码
SOLID原则
面向对象设计的五个原则,可构建可维护、可扩展的软件。
S - 单一职责原则
一个类应该只有一个引起变化的原因。
// 错误 - 多个职责
class UserService {
createUser(data: UserData) { /* ... */ }
sendEmail(user: User, message: string) { /* ... */ }
generateReport(users: User[]) { /* ... */ }
validateEmail(email: string) { /* ... */ }
}
// 正确 - 每个类单一职责
class UserService {
constructor(
private repository: UserRepository,
private validator: UserValidator
) {}
createUser(data: UserData): User {
this.validator.validate(data);
return this.repository.save(data);
}
}
class EmailService {
send(to: string, message: string) { /* ... */ }
}
class UserReportGenerator {
generate(users: User[]): Report { /* ... */ }
}
class EmailValidator {
validate(email: string): boolean { /* ... */ }
}
适用时机: 如果用"和"来描述一个类(UserService创建用户和发送邮件和…),就拆分它。
O - 开闭原则
对扩展开放,对修改关闭。
// 错误 - 必须修改类来添加新类型
class PaymentProcessor {
process(payment: Payment) {
if (payment.type === 'credit') {
// 处理信用卡
} else if (payment.type === 'paypal') {
// 处理PayPal
} else if (payment.type === 'crypto') {
// 处理加密货币 - 必须修改!
}
}
}
// 正确 - 无需修改即可扩展
interface PaymentMethod {
process(amount: number): Promise<Receipt>;
}
class CreditCardPayment implements PaymentMethod {
async process(amount: number): Promise<Receipt> {
// 信用卡逻辑
}
}
class PayPalPayment implements PaymentMethod {
async process(amount: number): Promise<Receipt> {
// PayPal逻辑
}
}
// 添加加密货币支付无需修改现有代码
class CryptoPayment implements PaymentMethod {
async process(amount: number): Promise<Receipt> {
// 加密货币逻辑
}
}
class PaymentProcessor {
process(method: PaymentMethod, amount: number) {
return method.process(amount);
}
}
适用时机: 当添加新功能需要修改现有已测试的代码时。
L - 里氏替换原则
子类型必须能够替换其基类型。
// 错误 - Square违反了Rectangle的契约
class Rectangle {
constructor(protected width: number, protected height: number) {}
setWidth(width: number) { this.width = width; }
setHeight(height: number) { this.height = height; }
getArea() { return this.width * this.height; }
}
class Square extends Rectangle {
setWidth(width: number) {
this.width = width;
this.height = width; // 违反预期!
}
setHeight(height: number) {
this.width = height;
this.height = height; // 违反预期!
}
}
// 这会出错:
function doubleWidth(rect: Rectangle) {
const originalHeight = rect.getArea() / rect.width;
rect.setWidth(rect.width * 2);
// 对于Square,高度也被加倍 - 意料之外!
}
// 正确 - 分离的层次结构
interface Shape {
getArea(): number;
}
class Rectangle implements Shape {
constructor(private width: number, private height: number) {}
getArea() { return this.width * this.height; }
}
class Square implements Shape {
constructor(private side: number) {}
getArea() { return this.side * this.side; }
}
适用时机: 如果子类重写改变了调用者依赖的行为。
I - 接口隔离原则
客户端不应该依赖它不使用的接口。
// 错误 - 臃肿的接口
interface Worker {
work(): void;
eat(): void;
sleep(): void;
attendMeeting(): void;
writeReport(): void;
}
class Robot implements Worker {
work() { /* ... */ }
eat() { throw new Error('机器人不需要吃饭'); } // 被迫实现!
sleep() { throw new Error('机器人不需要睡觉'); }
attendMeeting() { throw new Error('不适用'); }
writeReport() { throw new Error('不适用'); }
}
// 正确 - 隔离的接口
interface Workable {
work(): void;
}
interface Feedable {
eat(): void;
}
interface Sleepable {
sleep(): void;
}
interface MeetingAttendee {
attendMeeting(): void;
}
class Human implements Workable, Feedable, Sleepable, MeetingAttendee {
work() { /* ... */ }
eat() { /* ... */ }
sleep() { /* ... */ }
attendMeeting() { /* ... */ }
}
class Robot implements Workable {
work() { /* ... */ }
}
适用时机: 当类实现不需要的方法,或抛出"未实现"错误时。
D - 依赖倒置原则
依赖抽象,而不是具体实现。
// 错误 - 高层依赖低层
class MySQLDatabase {
query(sql: string) { /* ... */ }
}
class UserRepository {
private db = new MySQLDatabase(); // 紧耦合!
findById(id: string) {
return this.db.query(`SELECT * FROM users WHERE id = '${id}'`);
}
}
// 正确 - 都依赖抽象
interface Database {
query<T>(sql: string): Promise<T>;
}
class MySQLDatabase implements Database {
async query<T>(sql: string): Promise<T> { /* ... */ }
}
class PostgreSQLDatabase implements Database {
async query<T>(sql: string): Promise<T> { /* ... */ }
}
class UserRepository {
constructor(private db: Database) {} // 依赖注入!
findById(id: string) {
return this.db.query(`SELECT * FROM users WHERE id = '${id}'`);
}
}
// 轻松切换实现
const repo = new UserRepository(new PostgreSQLDatabase());
适用时机: 当测试困难,或修改一个模块会破坏其他模块时。
SOLID实践
识别违反情况
| 原则 | 代码异味 |
|---|---|
| SRP | 类有许多不相关的方法 |
| OCP | 添加功能需要修改现有代码 |
| LSP | 子类抛出"不支持"或行为不同 |
| ISP | 类实现它不使用的方法 |
| DIP | new关键字散落在业务逻辑中 |
应用SOLID
- 从简单开始 - 不要一开始就过度设计
- 需要时重构 - 感受到痛点时再应用
- 使用依赖注入 - 使DIP自然实现
- 优先组合 - 而非继承(有助于LSP)
- 编写小接口 - 比后来拆分更容易
平衡
SOLID是指导,不是法律。过度应用会导致:
- 太多微小类
- 难以理解的间接层
- 目前没人需要的抽象
在以下情况应用SOLID:
- 代码难以测试
- 变化在代码库中产生连锁反应
- 多个地方需要类似修改
- 正在添加某个东西的第3个变体
检查清单
- [ ] 每个类是否有单一、明确的目的?
- [ ] 能否在不修改现有代码的情况下添加功能?
- [ ] 子类能否安全替换父类?
- [ ] 接口是否专注且最小化?
- [ ] 依赖是否注入,而不是内部创建?