SOLID 是面向对象设计中的五大原则,不管什么面向对象的语言, 这个准则都很重要,如果你没听说过,赶紧先学一下。它可以提高代码的可维护性、可扩展性和可读性,使代码更加健壮、易于测试和扩展。SOLID 代表以下五个设计原则:
- S - 单一职责原则(Single Responsibility Principle, SRP)
- O - 开闭原则(Open/Closed Principle, OCP)
- L - 里氏替换原则(Liskov Substitution Principle, LSP)
- I - 接口隔离原则(Interface Segregation Principle, ISP)
- D - 依赖倒置原则(Dependency Inversion Principle, DIP)
1. 单一职责原则(SRP)
一个类应该仅有一个引起它变化的原因。
即 一个类应该仅负责一个功能,否则后期修改代码时,可能会影响其他无关功能。
❌ 违反 SRP 的例子
class Report {
public:void generateReport() { /* 生成报表 */ }void printReport() { /* 打印报表 */ }void saveToFile() { /* 保存到文件 */ }
};
问题:
Report
负责 生成报表、打印报表 和 保存报表,违反了 SRP。
✅ 遵循 SRP 的改进
class Report {
public:void generateReport() { /* 生成报表 */ }
};class ReportPrinter {
public:void printReport(Report& report) { /* 打印报表 */ }
};class ReportSaver {
public:void saveToFile(Report& report) { /* 保存到文件 */ }
};
改进点:
Report
只负责生成报表ReportPrinter
负责打印ReportSaver
负责存储
这样每个类的变更都不会影响其他功能,符合 SRP。
2. 开闭原则(OCP)
软件实体(类、模块、函数)应该对扩展开放,对修改关闭。
即:新增功能时,应该通过扩展代码,而不是修改已有代码。
❌ 违反 OCP 的例子
class PaymentProcessor {
public:void processPayment(std::string paymentType) {if (paymentType == "CreditCard") {// 处理信用卡支付} else if (paymentType == "PayPal") {// 处理 PayPal 支付}}
};
问题:
- 如果新增 Apple Pay,需要修改
processPayment()
,违反 OCP。 - 代码越复杂,修改的风险越高。
✅ 遵循 OCP 的改进
class Payment {
public:virtual void pay() = 0;virtual ~Payment() = default;
};class CreditCardPayment : public Payment {
public:void pay() override { /* 处理信用卡支付 */ }
};class PayPalPayment : public Payment {
public:void pay() override { /* 处理 PayPal 支付 */ }
};class PaymentProcessor {
public:void processPayment(Payment& payment) {payment.pay();}
};
改进点:
- 新增支付方式时,不需要修改
PaymentProcessor
,只需新增一个类(符合 OCP)。 - 通过 多态 使代码更加灵活。
3. 里氏替换原则(LSP)
子类必须能够替换基类,并且不会破坏程序的正确性。
❌ 违反 LSP 的例子
class Bird {
public:virtual void fly() { /* 飞行逻辑 */ }
};class Penguin : public Bird {
public:void fly() override {throw std::runtime_error("企鹅不会飞!");}
};
问题:
Penguin
继承了Bird
,但企鹅不会飞!Penguin::fly()
违背了父类的逻辑,可能导致程序崩溃。
✅ 遵循 LSP 的改进
class Bird {
public:virtual void move() = 0;
};class FlyingBird : public Bird {
public:void move() override { /* 飞行逻辑 */ }
};class Penguin : public Bird {
public:void move() override { /* 企鹅走路 */ }
};
改进点:
- 抽象出
FlyingBird
和Penguin
,使Penguin
不继承fly()
,从而避免违反 LSP。
4. 接口隔离原则(ISP)
不应该强迫类实现它们不需要的接口。
即:一个接口不应该承担过多职责,而应该拆分成多个专门的接口。
❌ 违反 ISP 的例子
class Worker {
public:virtual void work() = 0;virtual void eat() = 0;
};
class Robot : public Worker {
public:void work() override { /* 机器人工作 */ }void eat() override { throw std::runtime_error("机器人不吃饭!"); }
};
问题:
Robot
不需要eat()
,但仍然要实现它,违反 ISP。
✅ 遵循 ISP 的改进
class Workable {
public:virtual void work() = 0;
};class Eatable {
public:virtual void eat() = 0;
};class Human : public Workable, public Eatable {
public:void work() override { /* 人工作 */ }void eat() override { /* 人吃饭 */ }
};class Robot : public Workable {
public:void work() override { /* 机器人工作 */ }
};
改进点:
- 把
Worker
拆分成Workable
和Eatable
,避免不必要的实现。
5. 依赖倒置原则(DIP)
高层模块不应该依赖低层模块,而应该依赖于抽象(接口)。
❌ 违反 DIP 的例子
class LEDLight {
public:void turnOn() { /* 打开 LED 灯 */ }
};class Switch {
private:LEDLight light;
public:void operate() { light.turnOn(); }
};
问题:
Switch
直接依赖LEDLight
,如果要支持 白炽灯,必须修改Switch
,违反 OCP 和 DIP。
✅ 遵循 DIP 的改进
class Light {
public:virtual void turnOn() = 0;virtual ~Light() = default;
};class LEDLight : public Light {
public:void turnOn() override { /* 打开 LED 灯 */ }
};class IncandescentLight : public Light {
public:void turnOn() override { /* 打开白炽灯 */ }
};class Switch {
private:Light& light;
public:Switch(Light& l) : light(l) {}void operate() { light.turnOn(); }
};
改进点:
Switch
依赖Light
接口,不依赖具体的LEDLight
,符合 DIP。- 以后要支持新灯泡 不修改 Switch 代码。
总结
原则 | 含义 | 好处 |
---|---|---|
SRP | 一个类只做一件事 | 降低耦合,提高可维护性 |
OCP | 类应对扩展开放,对修改关闭 | 增加功能时不修改已有代码 |
LSP | 子类可以替换父类,不影响功能 | 确保继承不会破坏系统 |
ISP | 接口应该小而精,不要强迫实现不需要的功能 | 降低实现负担,提高灵活性 |
DIP | 依赖接口,不依赖具体实现 | 提高可扩展性,降低耦合 |
结论
遵循 SOLID 原则可以写出更好的代码,使系统更易扩展、维护和测试。在设计类和模块时,应尽量减少耦合,提高可复用性,避免不必要的修改。