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 原則可以寫出更好的代碼,使系統更易擴展、維護和測試。在設計類和模塊時,應盡量減少耦合,提高可復用性,避免不必要的修改。