一、設計模式
設計模式(Design Patterns)是軟件開發中反復出現問題的解決方案的通用描述。
它是經過總結、提煉的高效代碼結構和設計方案,幫助開發者寫出更靈活、可維護和可擴展的代碼。
優點 | 注意點 |
---|---|
規范代碼結構,提高開發效率 | 設計模式不是銀彈,需結合實際使用 |
促進代碼復用和靈活擴展 | 濫用設計模式會導致過度設計 |
便于團隊溝通和代碼維護 | 理解設計模式背后的設計原則更重要 |
二、設計模式應用原則
原則 | 英文全稱 | 核心思想 |
---|---|---|
單一職責原則(SRP) | Single Responsibility Principle | 一個類只負責一項職責,如變化只因一個原因而發生 |
開閉原則(OCP) | Open/Closed Principle | 對擴展開放,對修改關閉,鼓勵使用抽象與繼承 |
里氏替換原則(LSP) | Liskov Substitution Principle | 子類對象能替換父類對象而不影響程序正確性 |
接口隔離原則(ISP) | Interface Segregation Principle | 不應強迫用戶依賴他們不使用的方法 |
依賴倒置原則(DIP) | Dependency Inversion Principle | 依賴于抽象而非具體實現(低層依賴高層) |
合成復用原則(CRP) | Composite Reuse Principle | 優先使用對象組合而非類繼承 |
最少知識原則(LoD) | Law of Demeter | 只與直接朋友通信,降低類之間耦合 |
迪米特法則(Demeter) | Law of Demeter(LoD 的別稱) | 同上,只交互需要交互的對象 |
三、設計模式的分類
設計模式一般分為三大類:
類別 | 作用 | 代表模式舉例 |
---|---|---|
創建型模式 | 關注對象的創建,簡化對象創建過程 | 單例(Singleton)、工廠(Factory)、抽象工廠(Abstract Factory)、建造者(Builder)、原型(Prototype) |
結構型模式 | 關注對象和類的組合,實現高效的結構設計 | 適配器(Adapter)、裝飾器(Decorator)、代理(Proxy)、橋接(Bridge)、組合(Composite)、享元(Flyweight)、外觀(Facade) |
行為型模式 | 關注對象之間的通信和職責分配 | 觀察者(Observer)、策略(Strategy)、命令(Command)、狀態(State)、職責鏈(Chain of Responsibility)、迭代器(Iterator)、中介者(Mediator)、備忘錄(Memento) |
常見設計模式有單例模式、工廠模式、觀察者模式、策略模式、代理模式、裝飾器模式等。
四、常見設計模式——單例模式(Singleton Pattern)
1. 概念
單例模式(Singleton Pattern)是一種創建型設計模式,目的是:
確保一個類只有一個實例,并提供一個全局訪問點。
- 單例模式提供 全局唯一實例控制,是一種非常常用的基礎模式;
- 在多線程環境下注意加鎖或使用現代 C++ 靜態變量機制;
- 避免濫用,防止造成代碼耦合和測試困難。
2. 使用場景
適用于以下場景:
場景描述 | 示例 |
---|---|
程序中只需要一個實例 | 日志記錄器、線程池、數據庫連接池 |
全局共享訪問 | 配置管理器、計數器、注冊表 |
控制資源訪問 | 限制類實例個數,控制某些資源(如IO、GPU等) |
3. 核心特點
- 構造函數私有化,防止外部創建對象。
- 提供靜態方法,返回唯一實例。
- 類中保存唯一實例的靜態指針。
4. C++ 中的經典實現(線程安全 + 懶漢式)
#include <iostream>
using namespace std;
#include <mutex>class Singleton {
private:// 構造函數私有,防止外部構造Singleton(){cout << "構造 Singleton 對象" << endl;}// 禁用拷貝構造和賦值操作Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;static Singleton* instance; // 靜態指針static std::mutex mtx; // 線程安全鎖public:static Singleton* getInstance(){// 雙重檢查鎖定(Double-Check Locking)if (instance == nullptr){lock_guard<mutex> lock(mtx); // 線程鎖if (instance == nullptr){instance = new Singleton();}}return instance;}void show(){cout << "這是 Singleton 實例" << endl;}
};// 初始化靜態成員
Singleton* Singleton::instance = nullptr;
mutex Singleton::mtx;// 測試
int main() {Singleton* s1 = Singleton::getInstance();Singleton* s2 = Singleton::getInstance();s1->show();if (s1 == s2)cout << "兩個實例相同,單例模式生效!" << endl;return 0;
}
注意:
= delete
是 C++11 引入的一種機制,用來顯式標記某個函數(包括構造、賦值、析構、普通函數)不允許使用。- 如果調用被
= delete
的函數,編譯時就會報錯,這比寫成私有函數 + 空實現更安全、更清晰。
5. 變體說明
實現方式 | 是否線程安全 | 是否懶加載 | 特點 |
---|---|---|---|
懶漢式(如上) | ? | ? | 第一次調用時創建實例,性能較優 |
餓漢式(靜態變量) | ? | ? | 程序加載時即創建,簡單但可能浪費資源 |
C++11 靜態局部變量 | ?(自動線程安全) | ? | 推薦:C++11后局部靜態變量是線程安全的 |
五、工廠模式(Factory Pattern)
1. 概念
工廠模式是一種常見的創建型設計模式,核心目的是:
將對象的創建邏輯從使用者中解耦,由“工廠”統一創建對象。
2. 主要分類
模式名稱 | 簡要說明 |
---|---|
簡單工廠模式 | 一個工廠類決定創建哪一種產品類 |
工廠方法模式 | 每個產品由一個具體工廠類負責創建 |
抽象工廠模式 | 生產多個產品族(多個產品等級結構),創建一組相關對象 |
3. 適用場景
- 對象創建過程復雜或變化頻繁;
- 代碼中存在大量
new
操作,難以維護; - 系統要根據不同條件動態決定創建哪一類對象。
4. 示例——工廠方法模式(Factory Method Pattern)
場景:不同的圖形類(Circle
, Rectangle
),由對應的工廠類生成。
產品抽象類:
class Shape {
public:virtual void draw() = 0; // 純虛函數virtual ~Shape() = default;
};
具體產品類:
class Circle : public Shape {
public:void draw() override {std::cout << "繪制圓形" << std::endl;}
};class Rectangle : public Shape {
public:void draw() override {std::cout << "繪制矩形" << std::endl;}
};
工廠抽象類:
class ShapeFactory {
public:virtual Shape* createShape() = 0; // 創建產品接口virtual ~ShapeFactory() = default;
};
具體工廠類:
class CircleFactory : public ShapeFactory {
public:Shape* createShape() override {return new Circle();}
};class RectangleFactory : public ShapeFactory {
public:Shape* createShape() override {return new Rectangle();}
};
客戶端使用:
int main() {ShapeFactory* factory;factory = new CircleFactory();Shape* circle = factory->createShape();circle->draw();delete circle;delete factory;factory = new RectangleFactory();Shape* rectangle = factory->createShape();rectangle->draw();delete rectangle;delete factory;return 0;
}
5. 優缺點分析
優點 | 缺點 |
---|---|
封裝對象創建,調用者無需關心構造細節 | 每新增一個產品類就需要新增一個工廠類(類爆炸) |
易于擴展,遵循開閉原則 | 簡單場景下可能顯得過度設計 |
可以靈活切換不同產品,支持多態創建和調用 | 抽象層次增加,系統結構更復雜 |
六、觀察者模式(Observer Pattern)
1. 概念
觀察者模式是一種行為型模式,用于建立一種一對多的依賴關系:
當一個對象(主題/被觀察者)的狀態發生變化時,所有依賴于它的對象(觀察者)都會自動收到通知并更新。
2. 應用場景
場景 | 示例 |
---|---|
狀態變化需要通知其他對象 | 圖形界面事件(按鈕點擊) |
發布-訂閱系統 | 消息隊列、郵件通知、新聞訂閱系統 |
數據模型與視圖同步 | MVC 模式中的 Model 和 View |
3. 結構組成
角色 | 職責說明 |
---|---|
Subject(主題) | 維護觀察者列表、添加/移除/通知觀察者 |
Observer(觀察者) | 接收通知并作出反應 |
ConcreteSubject | 實際的主題,狀態發生變化時通知觀察者 |
ConcreteObserver | 實際觀察者,實現更新邏輯 |
4. C++ 示例代碼
場景:氣象站(WeatherStation)數據變化,通知多個顯示設備(觀察者)。
抽象觀察者接口:
class Observer {
public:virtual void update(float temperature) = 0;virtual ~Observer() = default;
};
主題(Subject)接口:
#include <vector>class Subject {
public:virtual void attach(Observer* observer) = 0;virtual void detach(Observer* observer) = 0;virtual void notify() = 0;virtual ~Subject() = default;
};
具體主題類:
class WeatherStation : public Subject {
private:std::vector<Observer*> observers;float temperature;public:void setTemperature(float temp) {temperature = temp;notify(); // 數據變化時,通知所有觀察者}void attach(Observer* observer) override {observers.push_back(observer);}void detach(Observer* observer) override {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());}void notify() override {for (Observer* o : observers) {o->update(temperature);}}
};
具體觀察者類:
class PhoneDisplay : public Observer {
public:void update(float temperature) override {std::cout << "[Phone] 當前溫度:" << temperature << "°C" << std::endl;}
};class LEDDisplay : public Observer {
public:void update(float temperature) override {std::cout << "[LED] 顯示屏溫度:" << temperature << "°C" << std::endl;}
};
客戶端使用:
int main() {WeatherStation station;PhoneDisplay phone;LEDDisplay led;station.attach(&phone);station.attach(&led);station.setTemperature(25.5f);station.setTemperature(30.2f);return 0;
}
5. 優缺點分析
優點 | 缺點 |
---|---|
解耦:主題和觀察者之間松散耦合 | 可能出現通知鏈過長,影響性能 |
易擴展:可動態添加/刪除觀察者 | 無法保證通知順序,某些觀察者可能對順序敏感 |
符合“開閉原則”:新增觀察者不改動主題類 | 如果觀察者太多,頻繁通知可能帶來性能開銷 |
6. 實際應用
- Qt 信號槽機制;
- GUI 框架(按鈕監聽器);
- 消息發布/訂閱(如 Kafka、RabbitMQ);
- Excel 單元格間依賴(值變化時聯動)。
七、代理模式(Proxy Pattern)
1. 概念
代理模式屬于結構型設計模式,主要作用是:
為其他對象提供一種“代理”以控制對該對象的訪問。
即通過一個中介(代理對象)來間接訪問目標對象,可以增加訪問控制、延遲加載、資源控制等功能。
2. 適用場景
場景描述 | 示例 |
---|---|
遠程代理(Remote Proxy) | 遠程方法調用,如 RPC 框架 |
虛擬代理(Virtual Proxy) | 延遲加載大型對象,如圖片、視頻加載 |
安全代理(Protection Proxy) | 控制權限訪問,如權限驗證、安全審計 |
智能代理(Smart Proxy) | 附加額外操作,如引用計數、日志記錄等 |
3. 結構角色
角色 | 職責說明 |
---|---|
Subject | 抽象主題接口,定義目標行為 |
RealSubject | 真實對象,實現具體邏輯 |
Proxy | 代理對象,控制訪問真實對象,可增強功能 |
4. C++ 示例——訪問圖像的代理模式(虛擬代理)
抽象接口(Subject):
class Image {
public:virtual void display() = 0;virtual ~Image() = default;
};
真實對象(RealSubject):
class RealImage : public Image {
private:std::string filename;
public:RealImage(const std::string& file) : filename(file) {loadFromDisk();}void loadFromDisk() {std::cout << "加載圖片文件:" << filename << std::endl;}void display() override {std::cout << "顯示圖片:" << filename << std::endl;}
};
代理對象(Proxy):
class ProxyImage : public Image {
private:std::string filename;RealImage* realImage = nullptr;
public:ProxyImage(const std::string& file) : filename(file) {}void display() override {if (!realImage) {realImage = new RealImage(filename); // 延遲加載}realImage->display();}~ProxyImage() {delete realImage;}
};
客戶端使用:
int main() {Image* image = new ProxyImage("photo.jpg");std::cout << "第一次調用 display() :" << std::endl;image->display(); // 會加載 + 顯示std::cout << "第二次調用 display() :" << std::endl;image->display(); // 直接顯示,不再加載delete image;return 0;
}
5. 優缺點分析
優點 | 缺點 |
---|---|
控制對象訪問(權限、安全、遠程調用) | 增加系統復雜度 |
可延遲加載資源,提升性能(如虛擬代理) | 若過度使用,可能增加維護難度 |
可增加額外功能(如日志、緩存、引用計數等) | 被代理對象接口變化時,代理類也需修改 |
6. 實際應用
- 虛擬代理:大型圖像延遲加載(如網頁圖片懶加載);
- 安全代理:數據庫連接權限控制;
- 智能代理:引用計數類(如智能指針
std::shared_ptr
); - 遠程代理:RPC、gRPC 框架中的遠程服務調用。
八、策略模式(Strategy Pattern)
1. 概念
策略模式是一種典型的行為型設計模式,用于:
將一系列可互換的算法(或策略)封裝起來,使它們可以獨立于使用它們的客戶端自由切換。
2. 適用場景
適用場景 | 示例 |
---|---|
存在多個算法/行為,可以靈活切換 | 支付方式(微信/支付寶)、排序算法選擇等 |
使用 if-else / switch 過多 | 用策略類代替繁雜條件邏輯 |
系統需要在運行時動態選擇行為 | 游戲角色攻擊方式、文件壓縮算法(ZIP、RAR)等 |
3. 結構組成
角色 | 職責說明 |
---|---|
Strategy | 抽象策略類,定義算法接口 |
ConcreteStrategy | 具體策略類,實現不同算法或行為 |
Context | 上下文類,持有策略對象,供客戶端使用 |
4. C++ 示例——支付策略(微信、支付寶)
抽象策略類:
class PaymentStrategy {
public:virtual void pay(int amount) = 0;virtual ~PaymentStrategy() = default;
};
具體策略類:
class WeChatPay : public PaymentStrategy {
public:void pay(int amount) override {std::cout << "使用微信支付:" << amount << " 元" << std::endl;}
};class Alipay : public PaymentStrategy {
public:void pay(int amount) override {std::cout << "使用支付寶支付:" << amount << " 元" << std::endl;}
};
上下文類(使用策略):
class PaymentContext {
private:PaymentStrategy* strategy;public:PaymentContext(PaymentStrategy* strategy) : strategy(strategy) {}void setStrategy(PaymentStrategy* newStrategy) {strategy = newStrategy;}void pay(int amount) {strategy->pay(amount);}
};
客戶端使用示例:
int main() {WeChatPay wechat;Alipay alipay;PaymentContext context(&wechat);context.pay(100); // 使用微信支付context.setStrategy(&alipay);context.pay(200); // 使用支付寶支付return 0;
}
5. 優缺點分析
優點 | 缺點 |
---|---|
算法可以自由切換,互不影響,符合開閉原則 | 客戶端需要了解不同策略類以選擇使用 |
消除了大量 if-else ,邏輯更清晰 | 增加了類數量,每個策略都要寫一個類 |
可以在運行時動態選擇策略,實現更靈活的行為擴展 | 若算法內部差異很小,會導致重復代碼 |
6. 現實應用
- 排序器中選擇不同排序策略(快速排序、歸并排序等);
- 游戲中角色選擇攻擊行為(近戰、遠程、魔法);
- 視頻播放器根據網絡狀況切換清晰度策略。
九、裝飾器模式(Decorator Pattern)
1. 概念
裝飾器模式(Decorator)是一種結構型設計模式,用于:
在不改變原始類代碼的前提下,動態地為對象添加額外的功能。
2. 使用場景
使用場景 | 示例 |
---|---|
動態添加/移除對象功能 | I/O 流(如 std::iostream ) |
避免子類爆炸式擴展(繼承過多) | 代替多子類繼承組合 |
運行時靈活組合對象行為 | GUI 控件樣式、文本處理管道等 |
3. 結構角色
角色 | 職責說明 |
---|---|
Component | 抽象接口,定義可以裝飾的操作行為 |
ConcreteComponent | 具體對象,定義基本行為實現 |
Decorator | 抽象裝飾器,包含一個 Component 成員 |
ConcreteDecorator | 實際裝飾器,增加具體功能 |
4. C++ 示例——飲料加調料系統
抽象組件接口(Component):
class Beverage {
public:virtual std::string getDescription() = 0;virtual double cost() = 0;virtual ~Beverage() = default;
};
具體組件(ConcreteComponent):
class Coffee : public Beverage {
public:std::string getDescription() override {return "Coffee";}double cost() override {return 5.0;}
};
抽象裝飾器(Decorator):
class CondimentDecorator : public Beverage {
protected:Beverage* beverage;
public:CondimentDecorator(Beverage* b) : beverage(b) {}
};
具體裝飾器(ConcreteDecorator):
class Milk : public CondimentDecorator {
public:Milk(Beverage* b) : CondimentDecorator(b) {}std::string getDescription() override {return beverage->getDescription() + ", Milk";}double cost() override {return beverage->cost() + 1.5;}
};class Sugar : public CondimentDecorator {
public:Sugar(Beverage* b) : CondimentDecorator(b) {}std::string getDescription() override {return beverage->getDescription() + ", Sugar";}double cost() override {return beverage->cost() + 0.5;}
};
客戶端使用:
int main() {Beverage* beverage = new Coffee(); // 基礎咖啡beverage = new Milk(beverage); // 加牛奶beverage = new Sugar(beverage); // 加糖std::cout << "飲料描述: " << beverage->getDescription() << std::endl;std::cout << "總價: " << beverage->cost() << " 元" << std::endl;delete beverage; // 實際應逐層釋放return 0;
}
5. 優缺點分析
優點 | 缺點 |
---|---|
不改變原始類,實現“開閉原則” | 類數目可能增多 |
支持功能組合,靈活拓展 | 多層嵌套可能導致調試困難 |
替代繼承層級,避免子類爆炸式增長 | 對象創建較多(每層一個) |
6. 現實應用
- Java I/O 流(
BufferedInputStream
,DataInputStream
等); - 圖形組件樣式疊加(邊框、陰影、背景);
- 日志增強、權限控制模塊;
- C++ 中的流操作符(如
std::ostream
裝飾行為),
十、常用設計模式的比較
模式名稱 | 目的 | 典型應用場景 | 優點 | 缺點 |
---|---|---|---|---|
單例模式 | 保證一個類只有一個實例,并提供全局訪問點 | 配置管理器、線程池、數據庫連接池、日志管理器 | 控制實例數量、節省資源、提供全局訪問點 | 不易擴展、對測試不友好、多線程需加鎖維護單例安全 |
工廠模式 | 將對象的創建過程封裝起來,客戶端無需了解對象創建的具體細節 | 創建復雜對象、大量具有共性但不完全相同的對象 | 解耦創建和使用,增強擴展性和靈活性 | 增加系統復雜性,類數量增多 |
觀察者模式 | 建立一對多的依賴關系,一旦主題對象狀態變化,所有觀察者都會收到通知 | 消息訂閱系統、事件驅動模型、GUI 事件處理、MVC 模式 | 實現解耦,支持廣播通信,符合開閉原則 | 可能引發性能問題或通知混亂,觀察者之間存在隱式依賴 |
策略模式 | 將算法封裝為獨立的策略類,使其可以互換,以便在運行時選擇不同的行為 | 算法族(如排序、壓縮)、游戲 AI 策略、支付方式選擇 | 策略獨立、便于切換、符合開閉原則 | 增加類數量,客戶端必須了解不同策略的作用 |
代理模式 | 通過代理對象控制對目標對象的訪問,可添加額外控制邏輯(如權限、懶加載等) | 安全控制、延遲加載、遠程調用、對象池、日志記錄 | 控制對象訪問、增強原對象功能、不改變原有類 | 增加系統復雜性,可能引入性能開銷 |
裝飾器模式 | 動態地為對象添加額外職責,不修改其結構,實現行為擴展 | IO流處理、圖形組件增強、權限驗證、日志記錄、緩存處理 | 比繼承更靈活,職責細化、符合開閉原則 | 多層裝飾可能導致調試困難,創建大量裝飾類 |
使用建議:
- 要 控制對象數量 → 單例模式;
- 要 封裝對象創建邏輯 → 工廠模式;
- 要 對象間解耦并實時通知 → 觀察者模式;
- 要 算法/行為靈活切換 → 策略模式;
- 要 控制對象訪問/增強功能 → 代理模式;
- 要 在不改變原對象的基礎上擴展功能 → 裝飾器模式。