文章目錄
- 一、面向對象的優點和缺點
- 1.1 回答重點
- 1.2 擴展知識
- 二、面向對象的三大特點
- 2.1 回答重點
- 2.2 擴展知識
- 三、設計模式的六大原則
- 3.1 回答重點
- 3.1.1 單一職責原則(Single Responsibility Principle, SRP)
- 3.1.2 開放 - 封閉原則(Open-Closed Principle, OCP)
- 3.1.3 里氏替換原則(Liskov Substitution Principle, LSP)
- 3.1.4 依賴倒置原則(Dependency Inversion Principle, DIP)
- 3.1.5 接口隔離原則(Interface Segregation Principle, ISP)
- 3.1.6 迪米特法則(最少知識原則)(Law of Demeter, LoD)
- 3.2 擴展知識
- 四、單例模式
- 4.1 回答重點
- 4.1.1 常見實現方式(C++)
- 1. 餓漢式(線程安全,但可能浪費資源)
- 2. 懶漢式(C++11 后線程安全,推薦)
- 3. 雙重檢查鎖定(DCL,兼容舊標準)
- 4.1.2 適用場景
- 五、工廠方法模式
- 5.1 回答重點
- 5.2 擴展知識
- 5.2.1 簡單工廠模式(Simple Factory)
- 5.2.2 工廠方法模式(Factory Method)
- 5.2.3 抽象工廠模式(Abstract Factory)
- 六、適配器模式(Adapter)
- 6.1 回答重點
- 6.2 擴展知識
- 七、代理模式
- 7.1 回答重點
- 7.2 擴展知識
- 八、觀察者模式
- 8.1 回答重點
- 8.2 擴展知識
- 九、策略模式
- 9.1 回答重點
- 9.2 擴展知識
- 十、備忘錄模式
- 10.1 回答重點
- 10.2 擴展知識
- 十一、模板模式
- 11.1 回答重點
- 11.2 擴展知識
- 十二、狀態模式
- 12.1 回答重點
- 12.2 擴展知識
- 十三、 責任鏈模式
- 13.1 回答重點
- 13.2 擴展知識
- 十四、裝飾模式
- 14.1 回答重點
- 14.2 擴展知識
- 十五、命令模式
- 15.1 回答重點
- 15.2 擴展知識
一、面向對象的優點和缺點
1.1 回答重點
優點:
-
模塊化、代碼重用性高,便于維護和擴展。
-
封裝性提高了代碼的安全性和穩定性。
-
通過多態和繼承提高了代碼的靈活性和擴展性。
-
模擬現實世界,適合處理復雜問題。
缺點:
-
性能開銷較大,尤其在高性能要求的環境中。
-
代碼復雜性較高,深層繼承和耦合關系可能難以維護。
-
學習曲線較陡,面向對象的概念對于初學者較難理解。
-
并不適合所有問題領域,某些場景下面向過程或函數式編程更為合適。
面向對象編程在構建大型、復雜的系統中有明顯優勢,但開發時應避免過度設計,并在性能要求高的場合慎重選擇。
1.2 擴展知識
面向對象編程的優點
-
模塊化和代碼重用:
-
面向對象通過類和對象來組織代碼,每個類封裝了特定的功能。類可以被復用,避免重復代碼。這種模塊化使得開發和維護更加簡便。
-
繼承(Inheritance)允許子類繼承父類的屬性和行為,進一步提升代碼的復用性。
-
-
代碼可維護性:
-
OOP 強調封裝(Encapsulation),使得對象的內部實現細節對外界隱藏,只通過公開的接口訪問對象。這降低了代碼復雜性,提高了系統的可維護性。
-
當代碼需要修改時,封裝的屬性和方法減少了對其他模塊的影響,降低了修改代碼時產生的副作用。
-
-
提高代碼的靈活性和可擴展性:
-
通過多態性(Polymorphism),不同對象可以以一致的方式對外提供接口,而實際的行為可以根據對象的具體類型有所不同。這使得代碼更加靈活,可以根據需求擴展,而不需要修改現有代碼。
-
抽象類和接口允許程序員定義抽象的行為,從而在系統需要擴展時,提供新的實現類,保持系統的靈活性。
-
-
提高軟件開發效率:
-
OOP 的模塊化和封裝使得團隊協作更為高效,不同團隊成員可以分別負責不同類的開發。
-
開發大型項目時,面向對象編程的結構化設計使得系統更加直觀易懂,開發人員可以清晰地看到系統的設計結構。
-
-
自然模擬現實世界問題:
- 面向對象的模型更貼近現實世界的事物和行為,使得復雜問題的建模變得直觀。開發人員可以通過對象、屬性和方法更自然地解決復雜問題。
面向對象編程的缺點
-
性能開銷:
-
面向對象的抽象層次較高,帶來了性能上的開銷。每個對象的創建和銷毀都需要內存分配和垃圾回收管理,方法調用也涉及更多的間接操作,可能會影響系統的性能。
-
尤其是在高性能和資源受限的應用(如嵌入式系統)中,OOP 的性能可能不如基于過程的編程效率高。
-
-
復雜性:
-
OOP 的設計思想和機制(如繼承、封裝、多態等)使得代碼結構復雜,尤其是在處理大型系統或深層繼承關系時,理解類之間的關系可能變得困難。
-
過度使用繼承和多態可能導致系統變得難以理解和維護(如“繼承層次過深”問題)。
-
-
類的依賴性和耦合性:
-
類與類之間的依賴關系可能變得復雜,如果一個類的設計不合理,可能會導致高度耦合,降低了系統的靈活性和可擴展性。類之間的強耦合會導致修改一個類時,不得不修改依賴該類的其他類。
-
在設計大型系統時,需要仔細考慮類之間的依賴性,以避免緊耦合的結構。
-
-
學習曲線陡峭:
-
對于初學者來說,面向對象的概念(如類、對象、繼承、多態等)并不直觀,學習 OOP 需要一定的時間和經驗。
-
與面向過程編程相比,OOP 的設計模式、抽象概念等可能更難掌握。
-
-
不適合所有問題領域:
- OOP 更適合復雜、需要模擬現實世界對象關系的問題領域,對于某些計算密集型或簡單任務的應用,面向對象可能顯得過于復雜,反而不如函數式編程或面向過程編程來得簡潔高效。
二、面向對象的三大特點
2.1 回答重點
封裝:
? 就是將客觀事物抽象為邏輯實體,實體的屬性和功能相結合,形成一個有機的整體。并對實體的屬性和功能實現進行訪問控制,向信任的實體開放,對不信任的實體隱藏。,通過開放的外部接口即可訪問,無需知道功能如何實現。
其目的是:
-
可隱藏實體實現的細節。
-
提高安全性,設定訪問控制,只允許具有特定權限的使用者調用。
-
簡化編程,調用方無需知道功能是怎么實現的,即可調用。
繼承:
? 在繼承機制下形成有層級的類,使得低層級的類可以延用高層級類的特征和方法。
繼承的實現方式有兩種:實現繼承、接口繼承。
-
實現繼承:直接使用基類公開的屬性和方法,無需額外編碼。
-
接口繼承:僅使用接口公開的屬性和方法名稱,需要子類實現。
也就是說,繼承有以下目的:
-
復用代碼,減少類的冗余代碼,減少開發工作量。
-
使得類與類之間產生關系,為多態的實現打下基礎。
多態:
? 是指一個類的同名方法,在不同情況下的實現細節不同。多態機制實現不同的內部實現結構共用同一個外部接口。
也就是說,多態有以下目的:
-
一個外部接口可被多個同類使用。
-
不同對象調用同個方法,可有不同實現。
2.2 擴展知識
pass
三、設計模式的六大原則
3.1 回答重點
3.1.1 單一職責原則(Single Responsibility Principle, SRP)
定義:一個類應該僅有一個引起它變化的原因,即一個類只負責一項具體職責。
核心:降低類的復雜度,避免一個類承擔過多職責導致的 “牽一發而動全身”。
// 錯誤示例:一個類同時處理訂單計算和日志記錄(兩個職責)
class Order {
public:void calculateTotal() { /* 計算訂單總額 */ }void saveToFile() { /* 保存訂單到文件 */ }void logOperation() { /* 記錄操作日志 */ } // 不屬于訂單核心職責
};// 正確示例:拆分日志職責到專門的類
class Logger { // 單一職責:僅處理日志
public:static void log(const std::string& msg) { /* 寫日志到文件 */ }
};class Order { // 單一職責:僅處理訂單業務
public:void calculateTotal() { /* 計算總額 */ }void saveToFile() { Logger::log("訂單已保存"); // 委托日志類處理日志}
};
3.1.2 開放 - 封閉原則(Open-Closed Principle, OCP)
定義:軟件實體(類、模塊、函數等)應對擴展開放,對修改關閉。即新增功能時應通過擴展實現,而非修改原有代碼。
核心:通過抽象隔離變化,提高系統穩定性。
// 抽象基類(定義穩定接口,對修改關閉)
class Payment {
public:virtual void pay(double amount) = 0; // 支付接口virtual ~Payment() = default;
};// 具體實現:支付寶(現有功能)
class Alipay : public Payment {
public:void pay(double amount) override {std::cout << "支付寶支付:" << amount << "元\n";}
};// 擴展新功能:微信支付(無需修改原有代碼,對擴展開放)
class WechatPay : public Payment {
public:void pay(double amount) override {std::cout << "微信支付:" << amount << "元\n";}
};// 支付管理器(依賴抽象,無需修改)
class PaymentManager {
public:void processPayment(Payment* payment, double amount) {payment->pay(amount); // 自動適配新支付方式}
};
3.1.3 里氏替換原則(Liskov Substitution Principle, LSP)
定義:所有引用基類的地方必須能透明地使用其子類的對象,且替換后不改變原有程序的正確性。
核心:子類必須遵守基類的行為約定,確保繼承體系的邏輯一致性。
// 基類:Rectangle(長方形)
class Rectangle {
protected:int width, height;
public:Rectangle(int w, int h) : width(w), height(h) {}virtual void setWidth(int w) { width = w; }virtual void setHeight(int h) { height = h; }int getArea() { return width * height; }
};// 錯誤示例:Square(正方形)繼承Rectangle,違反LSP
class Square : public Rectangle {
public:Square(int s) : Rectangle(s, s) {}// 子類修改了基類的行為約定(寬高必須相等)void setWidth(int w) override { width = height = w; }void setHeight(int h) override { width = height = h; }
};// 問題:傳入Square會導致邏輯錯誤
void testRectangle(Rectangle* r) {r->setWidth(2);r->setHeight(3);// 預期面積6,但Square實際返回9(2×2被3覆蓋為3×3)std::cout << "面積:" << r->getArea() << "\n";
}// 正確做法:Square不繼承Rectangle,單獨設計或引入新基類
3.1.4 依賴倒置原則(Dependency Inversion Principle, DIP)
定義:高層模塊不應依賴低層模塊,兩者都應依賴抽象;抽象不應依賴細節,細節應依賴抽象。
核心:通過抽象接口解耦高層與低層,提高系統靈活性。
// 抽象接口(抽象,不依賴細節)
class IDataStorage {
public:virtual void save(const std::string& data) = 0;virtual ~IDataStorage() = default;
};// 低層模塊:具體實現(依賴抽象)
class FileStorage : public IDataStorage {
public:void save(const std::string& data) override {std::cout << "保存到文件:" << data << "\n";}
};class DatabaseStorage : public IDataStorage {
public:void save(const std::string& data) override {std::cout << "保存到數據庫:" << data << "\n";}
};// 高層模塊:業務邏輯(依賴抽象,不依賴具體實現)
class UserService {
private:IDataStorage* storage; // 依賴接口,而非具體存儲方式
public:// 構造注入,靈活切換存儲方式UserService(IDataStorage* s) : storage(s) {}void saveUser(const std::string& user) {storage->save(user); // 無需關心具體存儲細節}
};
3.1.5 接口隔離原則(Interface Segregation Principle, ISP)
定義:客戶端不應被迫依賴它不需要的接口。即應將龐大的接口拆分為多個小而專一的接口,讓客戶端只依賴必要的接口。
核心:避免 “胖接口” 導致的不必要依賴。
// 錯誤示例:臃腫的接口包含所有可能方法
class IWorker {
public:virtual void work() = 0; // 工作virtual void eat() = 0; // 吃飯(機器人不需要)virtual void charge() = 0; // 充電(人類不需要)
};// 人類被迫實現不需要的charge()
class HumanWorker : public IWorker {
public:void work() override { /* 工作 */ }void eat() override { /* 吃飯 */ }void charge() override { /* 空實現或異常,不合理 */ }
};// 正確示例:拆分接口為專用接口
class IWorkable {
public:virtual void work() = 0;
};class IEatable {
public:virtual void eat() = 0;
};class IChargeable {
public:virtual void charge() = 0;
};// 人類實現需要的接口
class HumanWorker : public IWorkable, public IEatable {
public:void work() override { /* 工作 */ }void eat() override { /* 吃飯 */ }
};// 機器人實現需要的接口
class RobotWorker : public IWorkable, public IChargeable {
public:void work() override { /* 工作 */ }void charge() override { /* 充電 */ }
};
3.1.6 迪米特法則(最少知識原則)(Law of Demeter, LoD)
定義:一個對象應該對其他對象保持最少的了解,只與直接朋友(成員變量、方法參數、返回值)通信,避免訪問 “朋友的朋友”。
**大話設計模式:**如果兩個類不必彼此直接通信,那么這兩個類就不應當發生直接的相互作用。如果其中一個類需要調用另一個類的某個方法的話,可以通過第三者轉發這個調用。
核心:減少對象間的耦合,降低系統復雜度。
class Department { // 部門類
private:std::string name;
public:Department(const std::string& n) : name(n) {}std::string getName() const { return name; }
};class Employee { // 員工類(直接朋友:Department)
private:Department* dept;
public:Employee(Department* d) : dept(d) {}// 錯誤:暴露內部依賴,讓外部直接訪問"朋友的朋友"Department* getDepartment() const { return dept; }// 正確:提供高層接口,隱藏內部細節std::string getDepartmentName() const {return dept->getName(); // 內部處理與Department的交互}
};// 客戶端代碼
void printDept(Employee* emp) {// 錯誤:訪問"朋友的朋友"(Employee的朋友是Department)// std::cout << emp->getDepartment()->getName() << "\n";// 正確:只與直接朋友Employee通信std::cout << emp->getDepartmentName() << "\n";
}
總結:這六大原則的核心是 “高內聚、低耦合”。單一職責和接口隔離聚焦于 “如何拆分”,開放 - 封閉和依賴倒置指導 “如何擴展”,里氏替換保證 “繼承安全”,迪米特法則控制 “交互范圍”,共同支撐可維護、可擴展的代碼設計。
3.2 擴展知識
pass
四、單例模式
4.1 回答重點
單例模式是一種創建型設計模式,其核心是保證一個類在整個系統中僅有一個實例,并提供一個全局訪問點。這種模式常用于管理共享資源(如配置文件、數據庫連接池),避免資源競爭或重復初始化。
核心特點
- 唯一實例:類自身控制實例的創建,確保只有一個實例存在。
- 全局訪問:提供靜態方法(如
getInstance()
)供外部獲取實例,無需頻繁傳遞對象引用。 - 延遲初始化:通常在首次使用時才創建實例,避免不必要的資源消耗。
4.1.1 常見實現方式(C++)
1. 餓漢式(線程安全,但可能浪費資源)
class Singleton {
private:// 私有構造函數:禁止外部實例化Singleton() { /* 初始化資源 */ }// 禁止拷貝和移動Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 靜態實例(程序啟動時初始化)static Singleton instance;public:// 全局訪問點static Singleton& getInstance() {return instance;}
};// 類外初始化靜態成員
Singleton Singleton::instance;
- 優點:實現簡單,線程安全(C++ 中全局變量初始化在主線程完成)。
- 缺點:無論是否使用,實例都會在程序啟動時創建,可能浪費資源(如初始化耗時操作)。
2. 懶漢式(C++11 后線程安全,推薦)
class Singleton {
private:Singleton() { /* 初始化資源 */ }// 禁止拷貝和移動Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;public:// 全局訪問點:局部靜態變量(C++11后初始化線程安全)static Singleton& getInstance() {static Singleton instance; // 首次調用時初始化return instance;}
};
- 優點:延遲初始化(首次使用時創建),C++11 標準保證局部靜態變量初始化的線程安全性,無需手動加鎖。
- 缺點:依賴 C++11 及以上標準,舊編譯器可能不支持。
3. 雙重檢查鎖定(DCL,兼容舊標準)
#include <mutex>class Singleton {
private:Singleton() { /* 初始化資源 */ }Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 靜態指針(volatile避免編譯器優化)static volatile Singleton* instance;static std::mutex mtx;public:static Singleton& getInstance() {// 第一次檢查:避免每次加鎖(提高性能)if (instance == nullptr) {std::lock_guard<std::mutex> lock(mtx); // 加鎖// 第二次檢查:防止多線程同時通過第一次檢查if (instance == nullptr) {instance = new Singleton();}}return *const_cast<Singleton*>(instance);}
};// 類外初始化
volatile Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
- 優點:兼容 C++11 前標準,線程安全且延遲初始化。
- 缺點:實現復雜,需注意內存可見性(
volatile
)和鎖的開銷。
4.1.2 適用場景
- 資源管理器:如日志管理器、配置管理器(確保全局唯一的配置源)。
- 共享資源訪問:如數據庫連接池(控制連接數量,避免重復創建連接)。
- 工具類:無狀態的工具方法集合(如數學工具類,無需多個實例)。
- 優點:
- 確保唯一實例,減少資源占用;
- 全局訪問點簡化了對象獲取,避免參數傳遞冗余。
- 缺點:
- 違背單一職責原則(既負責自身業務,又負責實例管理);
- 可能隱藏依賴關系(代碼中直接調用
getInstance()
,難以追蹤依賴); - 測試困難(單例生命周期與程序一致,難以模擬不同測試環境)。
單例模式的核心是 “唯一實例 + 全局訪問”,C++ 中推薦使用懶漢式(局部靜態變量) 實現(C++11 及以上),簡潔且線程安全。其適用場景集中在共享資源管理,但需注意避免過度使用,以免導致代碼耦合度升高和測試困難。
五、工廠方法模式
5.1 回答重點
工廠方法模式是一種創建型設計模式,簡單工廠模式只有一個工廠類,負責創建所有產品,如果要添加新的產品,通常需要修改工廠類的代碼。而工廠方法模式引入了抽象工廠和具體工廠的概念,每個具體工廠只負責創建一個具體產品,添加新的產品只需要添加新的工廠類而無需修改原來的代碼,這樣就使得產品的生產更加靈活,支持擴展,符合開閉原則。
工廠方法模式分為以下幾個角色:
-
抽象工廠:一個接口,包含一個抽象的工廠方法(用于創建產品對象)。
-
具體工廠:實現抽象工廠接口,創建具體的產品。
-
抽象產品:定義產品的接口。
-
具體產品:實現抽象產品接口,是工廠創建的對象。
應用場景:
工廠方法模式使得每個工廠類的職責單一,每個工廠只負責創建一種產品,當創建對象涉及一系列復雜的初始化邏輯,而這些邏輯在不同的子類中可能有所不同時,可以使用工廠方法模式將這些初始化邏輯封裝在子類的工廠中。在現有的工具、庫中,工廠方法模式也有廣泛的應用,比如:
-
Spring 框架中的 Bean 工廠:通過配置文件或注解,Spring 可以根據配置信息動態地創建和管理對象。
-
JDBC 中的 Connection 工廠:在 Java 數據庫連接中,
DriverManager
使用工廠方法模式來創建數據庫連接。不同的數據庫驅動(如 MySQL、PostgreSQL 等)都有對應的工廠來創建連接。
示例代碼:
// 抽象積木接口
class Block {
public:virtual void produce() = 0;
};// 具體圓形積木實現
class CircleBlock : public Block {
public:void produce() override {std::cout << "Circle Block" << std::endl;}
};// 具體方形積木實現
class SquareBlock : public Block {
public:void produce() override {std::cout << "Square Block" << std::endl;}
};// 抽象積木工廠接口
class BlockFactory {
public:virtual Block* createBlock() = 0;
};// 具體圓形積木工廠實現
class CircleBlockFactory : public BlockFactory {
public:Block* createBlock() override {return new CircleBlock();}
};// 具體方形積木工廠實現
class SquareBlockFactory : public BlockFactory {
public:Block* createBlock() override {return new SquareBlock();}
};// 積木工廠系統
class BlockFactorySystem {
private:std::vector<Block*> blocks;public:void produceBlocks(BlockFactory* factory, int quantity) {for (int i = 0; i < quantity; i++) {Block* block = factory->createBlock();blocks.push_back(block);block->produce();}}const std::vector<Block*>& getBlocks() const {return blocks;}~BlockFactorySystem() {for (Block* block : blocks) {delete block;}}
};int main() {// 創建積木工廠系統BlockFactorySystem factorySystem;// 讀取生產次數int productionCount;std::cin >> productionCount;// 讀取每次生產的積木類型和數量for (int i = 0; i < productionCount; i++) {std::string blockType;int quantity;std::cin >> blockType >> quantity;if (blockType == "Circle") {factorySystem.produceBlocks(new CircleBlockFactory(), quantity);} else if (blockType == "Square") {factorySystem.produceBlocks(new SquareBlockFactory(), quantity);}}return 0;
}
5.2 擴展知識
工廠模式是創建型設計模式的一類,核心是將對象的創建與使用分離,通過 “工廠” 統一管理對象創建邏輯,降低代碼耦合。它包含三種具體形式:簡單工廠模式、工廠方法模式和抽象工廠模式,適用場景各有不同,下面分別介紹:
5.2.1 簡單工廠模式(Simple Factory)
定義:
- 由一個工廠類根據傳入的參數,決定創建哪一種具體產品的實例。它不屬于 GoF 的 23 種設計模式,但作為工廠模式的基礎,應用廣泛。
結構:
- 具體產品:實現同一接口的不同產品(如
ProductA
、ProductB
); - 工廠類:包含靜態方法,根據參數創建并返回具體產品實例。
示例(計算器工廠):
// 抽象產品:運算
class Operation {
public:virtual double calculate(double a, double b) = 0;virtual ~Operation() = default;
};// 具體產品:加法
class AddOperation : public Operation {
public:double calculate(double a, double b) override { return a + b; }
};// 具體產品:乘法
class MulOperation : public Operation {
public:double calculate(double a, double b) override { return a * b; }
};// 工廠類:根據參數創建產品
class OperationFactory {
public:static Operation* createOperation(char op) {switch (op) {case '+': return new AddOperation();case '*': return new MulOperation();default: throw std::invalid_argument("無效運算符");}}
};// 客戶端
int main() {Operation* op = OperationFactory::createOperation('+');std::cout << op->calculate(2, 3) << "\n"; // 輸出5delete op;return 0;
}
優缺點:
- 優點:簡單直觀,客戶端無需知道產品創建細節;
- 缺點:新增產品需修改工廠類(違反開放 - 封閉原則),工廠類職責過重,適合產品類型較少的場景。
5.2.2 工廠方法模式(Factory Method)
**定義:**定義一個創建產品的抽象接口(工廠),讓子類(具體工廠)決定實例化哪種產品。即 “一個產品對應一個工廠”,將創建邏輯延遲到子類。
結構:
- 抽象產品:所有產品的公共接口;
- 具體產品:實現抽象產品的具體類;
- 抽象工廠:聲明創建產品的純虛方法(工廠方法);
- 具體工廠:每個具體工廠對應一種產品,實現工廠方法。
// 抽象產品:文檔
class Document {
public:virtual void open() = 0;virtual ~Document() = default;
};// 具體產品:文本文檔
class TextDoc : public Document {
public:void open() override { std::cout << "打開文本文檔\n"; }
};// 具體產品:表格文檔
class SheetDoc : public Document {
public:void open() override { std::cout << "打開表格文檔\n"; }
};// 抽象工廠
class DocumentFactory {
public:virtual Document* createDocument() = 0; // 工廠方法virtual ~DocumentFactory() = default;
};// 具體工廠:文本工廠
class TextFactory : public DocumentFactory {
public:Document* createDocument() override { return new TextDoc(); }
};// 具體工廠:表格工廠
class SheetFactory : public DocumentFactory {
public:Document* createDocument() override { return new SheetDoc(); }
};// 客戶端
int main() {DocumentFactory* factory = new TextFactory();Document* doc = factory->createDocument();doc->open(); // 輸出:打開文本文檔delete doc;delete factory;return 0;
}
優缺點:
- 優點:新增產品只需添加具體產品和對應工廠(符合開放 - 封閉原則),職責單一;
- 缺點:類數量增多(每增一個產品對應一個工廠),適合產品類型較多且可能擴展的場景。
5.2.3 抽象工廠模式(Abstract Factory)
定義:提供一個接口,用于創建一系列相關或相互依賴的產品族,而無需指定具體類。即 “一個工廠生產多個關聯產品”,解決產品族的創建問題。
結構:
- 抽象產品族:多個相關產品的接口(如
ProductA
、ProductB
組成一個產品族); - 具體產品族:不同品牌 / 風格的產品族實現(如
Brand1ProductA
、Brand1ProductB
); - 抽象工廠:聲明創建每個產品的方法(覆蓋產品族中的所有產品);
- 具體工廠:每個工廠對應一個產品族,實現所有產品的創建方法。
C++ 示例(跨平臺 UI 組件)
// 抽象產品A:按鈕
class Button {
public:virtual void render() = 0;virtual ~Button() = default;
};// 抽象產品B:文本框
class TextBox {
public:virtual void render() = 0;virtual ~TextBox() = default;
};// 具體產品族1:Windows風格
class WinButton : public Button {
public:void render() override { std::cout << "Windows按鈕\n"; }
};
class WinTextBox : public TextBox {
public:void render() override { std::cout << "Windows文本框\n"; }
};// 具體產品族2:Mac風格
class MacButton : public Button {
public:void render() override { std::cout << "Mac按鈕\n"; }
};
class MacTextBox : public TextBox {
public:void render() override { std::cout << "Mac文本框\n"; }
};// 抽象工廠:聲明創建產品族的方法
class UIFactory {
public:virtual Button* createButton() = 0;virtual TextBox* createTextBox() = 0;virtual ~UIFactory() = default;
};// 具體工廠1:Windows工廠(生產Windows產品族)
class WinFactory : public UIFactory {
public:Button* createButton() override { return new WinButton(); }TextBox* createTextBox() override { return new WinTextBox(); }
};// 具體工廠2:Mac工廠(生產Mac產品族)
class MacFactory : public UIFactory {
public:Button* createButton() override { return new MacButton(); }TextBox* createTextBox() override { return new MacTextBox(); }
};// 客戶端
int main() {UIFactory* factory = new WinFactory(); // 切換為MacFactory即可換風格Button* btn = factory->createButton();TextBox* txt = factory->createTextBox();btn->render(); // 輸出:Windows按鈕txt->render(); // 輸出:Windows文本框delete btn;delete txt;delete factory;return 0;
}
優缺點:
- 優點:保證產品族內的產品兼容性(同一工廠生產的產品必然匹配),切換產品族只需更換工廠;
- 缺點:新增產品族成員(如增加 “復選框”)需修改所有工廠類(違反開放 - 封閉原則),適合產品族固定且需整體切換的場景(如跨平臺開發)。
三種模式的對比與適用場景
模式 | 核心特點 | 適用場景 | 擴展性(新增產品) |
---|---|---|---|
簡單工廠模式 | 單一工廠根據參數創建產品 | 產品類型少、變化少(如工具類) | 差(需修改工廠) |
工廠方法模式 | 一個產品對應一個工廠 | 產品類型多、可能擴展(如文檔類型) | 好(新增產品 + 工廠) |
抽象工廠模式 | 一個工廠生產產品族 | 需統一風格 / 品牌的相關產品(如跨平臺 UI) | 產品族內擴展差,新增族好 |
面試總結:
工廠模式的核心是 “分離對象創建與使用”,三種形式從簡單到復雜:
- 簡單工廠用一個工廠管理所有產品,適合簡單場景;
- 工廠方法用 “一對一” 的工廠 - 產品關系,平衡擴展性和復雜度;
- 抽象工廠解決 “產品族” 創建問題,適合關聯產品的統一管理。
選擇時需根據產品數量、關聯性和擴展需求決定,核心目標是降低耦合,提高代碼靈活性。
六、適配器模式(Adapter)
6.1 回答重點
**大話設計模式:**適配器模式就是,將一個類的接口轉化給客戶希望的另一個接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。
適配器模式Adapter
是一種結構型設計模式,它可以將一個類的接口轉換成客戶希望的另一個接口,主要目的是充當兩個不同接口之間的橋梁,使得原本接口不兼容的類能夠一起工作。
適配器可以分為以下幾個基本角色:
-
目標接口
Target
: 客戶端希望使用的接口 -
適配器類
Adapter
: 實現客戶端使用的目標接口,持有一個需要適配的類實例。 -
被適配者
Adaptee
: 需要被適配的類
應用場景:
在開發過程中,適配器模式往往扮演者“補救”和“擴展”的角色:
-
當使用一個已經存在的類,但是它的接口與你的代碼不兼容時,可以使用適配器模式。
-
在系統擴展階段需要增加新的類時,并且類的接口和系統現有的類不一致時,可以使用適配器模式。
使用適配器模式可以將客戶端代碼與具體的類解耦,客戶端不需要知道被適配者的細節,客戶端代碼也不需要修改,這使得它具有良好的擴展性,但是這也勢必導致系統變得更加復雜。
具體來說,適配器模式有著以下應用:
-
不同的項目和庫可能使用不同的日志框架,不同的日志框架提供的API也不同,因此引入了適配器模式使得不同的API適配為統一接口。
-
Spring MVC中,
HandlerAdapter
接口是適配器模式的一種應用。它負責將處理器(Handler)適配到框架中,使得不同類型的處理器能夠統一處理請求。 -
在
.NET
中,DataAdapter
用于在數據源(如數據庫)和DataSet
之間建立適配器,將數據從數據源適配到DataSet
中,以便在.NET應用程序中使用。
示例代碼:
#include <iostream>// USB 接口class USB {
public:virtual void charge() = 0;
};// TypeC 接口class TypeC {
public:virtual void chargeWithTypeC() = 0;
};// 適配器類class TypeCAdapter : public USB {
private:TypeC* typeC;public:TypeCAdapter(TypeC* typeC) : typeC(typeC) {}void charge() override {typeC->chargeWithTypeC();}
};// 新電腦類,使用 TypeC 接口
class NewComputer : public TypeC {
public:void chargeWithTypeC() override {std::cout << "TypeC" << std::endl;}
};// 適配器充電器類,使用 USB 接口
class AdapterCharger : public USB {
public:void charge() override {std::cout << "USB Adapter" << std::endl;}
};int main() {// 讀取連接次數int N;std::cin >> N;std::cin.ignore(); // 消耗換行符for (int i = 0; i < N; i++) {// 讀取用戶選擇int choice;std::cin >> choice;// 根據用戶的選擇創建相應對象if (choice == 1) {TypeC* newComputer = new NewComputer();newComputer->chargeWithTypeC();delete newComputer;} else if (choice == 2) {USB* usbAdapter = new AdapterCharger();usbAdapter->charge();delete usbAdapter;}}return 0;
}
6.2 擴展知識
適配器模式(Adapter Pattern)是一種結構型設計模式,用于解決兩個現有接口不兼容的問題。它通過創建一個中間層(適配器),將一個類的接口轉換成客戶端所期望的另一個接口,從而使原本因接口不匹配而無法協作的類能夠一起工作。簡單來說,適配器就像日常生活中的電源適配器,它能將 220V 的交流電轉換為電子設備需要的低壓直流電,讓不同接口的設備可以正常工作。
適配器模式的核心角色:
- 目標接口(Target):客戶端期望的接口標準
- 適配者(Adaptee):已存在但接口不兼容的類
- 適配器(Adapter):實現目標接口,并包裝適配者,完成接口轉換
#include <iostream>
#include <string>// 1. 目標接口(新系統期望的日志接口)
class NewLogger {
public:virtual ~NewLogger() = default;virtual void logInfo(const std::string& message) = 0;virtual void logError(const std::string& message) = 0;
};// 2. 適配者(舊系統的日志類,接口不兼容)
class LegacyLogger {
public:// 舊系統的日志方法,接口與新系統不同void writeLog(const std::string& level, const std::string& message) {std::cout << "Legacy Log [" << level << "]: " << message << std::endl;}
};// 3. 適配器(將舊日志接口轉換為新日志接口)
class LoggerAdapter : public NewLogger {
private:LegacyLogger* legacyLogger; // 持有適配者對象public:LoggerAdapter(LegacyLogger* logger) : legacyLogger(logger) {}// 實現新接口的方法,內部轉換為舊接口的調用void logInfo(const std::string& message) override {if (legacyLogger) {legacyLogger->writeLog("INFO", message);}}void logError(const std::string& message) override {if (legacyLogger) {legacyLogger->writeLog("ERROR", message);}}
};// 4. 客戶端代碼(只依賴新接口)
void clientCode(NewLogger* logger) {logger->logInfo("系統啟動成功");logger->logError("文件讀取失敗");
}int main() {// 創建舊系統日志對象LegacyLogger* legacyLogger = new LegacyLogger();// 創建適配器,包裝舊日志對象NewLogger* logger = new LoggerAdapter(legacyLogger);// 客戶端使用新接口,無需關心內部實現clientCode(logger);// 清理資源delete logger;delete legacyLogger;return 0;
}
- NewLogger:這是新系統期望的日志接口,定義了
logInfo
和logError
兩個方法 - LegacyLogger:這是舊系統的日志類,它只有一個
writeLog
方法,接口與新系統不兼容 - LoggerAdapter:適配器類,實現了
NewLogger
接口,并內部持有LegacyLogger
對象,在實現新接口方法時,將其轉換為對舊接口方法的調用 - 客戶端代碼:只依賴
NewLogger
接口,完全不需要知道LegacyLogger
的存在
Legacy Log [INFO]: 系統啟動成功
Legacy Log [ERROR]: 文件讀取失敗
這種模式的優勢在于:
- 復用了現有的
LegacyLogger
類,無需修改其代碼 - 客戶端代碼無需修改即可使用舊系統的功能
- 降低了新系統與舊系統之間的耦合度
- 符合 “開閉原則”,擴展新的適配器很方便
適配器模式特別適合在集成第三方庫、重構舊系統或需要復用現有類但接口不匹配的場景中使用。
七、代理模式
7.1 回答重點
大話設計模式: 為其他對象提供一種代理,以控制這個對象的訪問。
代理模式Proxy Pattern
是一種結構型設計模式,用于控制對其他對象的訪問。
在代理模式中,允許一個對象(代理)充當另一個對象(真實對象)的接口,以控制對這個對象的訪問。通常用于在訪問某個對象時引入一些間接層(中介的作用),這樣可以在訪問對象時添加額外的控制邏輯,比如限制訪問權限,延遲加載。
基本結構:
-
Subject(抽象主題): 抽象類,通過接口或抽象類聲明真實主題和代理對象實現的業務方法。
-
RealSubject(真實主題):定義了Proxy所代表的真實對象,是客戶端最終要訪問的對象。
-
Proxy(代理):包含一個引用,該引用可以是RealSubject的實例,控制對RealSubject的訪問,并可能負責創建和刪除RealSubject的實例。
應用場景:
- 比如說有一個文件加載的場景,為了避免直接訪問“文件”對象,我們可以新增一個代理對象,代理對象中有一個對“文件對象”的引用,在代理對象的
load
方法中,可以在訪問真實的文件對象之前進行一些操作,比如權限檢查,然后調用真實文件對象的load
方法,最后在訪問真實對象后進行其他操作,比如記錄訪問日志。
示例代碼:
// 抽象主題
class HomePurchase {
public:virtual void requestPurchase(int area) = 0;
};// 真實主題
class HomeBuyer : public HomePurchase {
public:void requestPurchase(int area) override {std::cout << "YES" << std::endl;}
};// 代理類
class HomeAgentProxy : public HomePurchase {
private:HomeBuyer homeBuyer;
public:void requestPurchase(int area) override {if (area > 100) {homeBuyer.requestPurchase(area);} else {std::cout << "NO" << std::endl;}}
};int main() {HomePurchase* buyerProxy = new HomeAgentProxy();int n;std::cin >> n;for (int i = 0; i < n; i++) {int area;std::cin >> area;buyerProxy->requestPurchase(area);}delete buyerProxy;return 0;
}
7.2 擴展知識
代理模式(Proxy Pattern)是一種結構型設計模式,它通過創建一個代理對象來控制對原始對象的訪問。代理對象可以在不改變原始對象接口的前提下,為其增加額外功能(如權限控制、緩存、延遲加載等),同時負責將請求轉發給原始對象。
#include <iostream>
#include <string>
#include <chrono>
#include <thread>// 1. 抽象主題:圖片接口
class Image {
public:virtual ~Image() = default;virtual void display() = 0; // 顯示圖片virtual std::string getFileName() const = 0; // 獲取文件名
};// 2. 真實主題:大型圖片(加載成本高)
class RealImage : public Image {
private:std::string fileName;// 模擬加載大型圖片的耗時操作void loadFromDisk() const {std::cout << "正在從磁盤加載圖片: " << fileName << " (耗時操作)...\n";std::this_thread::sleep_for(std::chrono::seconds(2)); // 模擬延遲}public:RealImage(const std::string& name) : fileName(name) {loadFromDisk(); // 真實圖片創建時立即加載}void display() override {std::cout << "顯示圖片: " << fileName << "\n";}std::string getFileName() const override {return fileName;}
};// 3. 代理:圖片代理(實現延遲加載和日志記錄)
class ImageProxy : public Image {
private:RealImage* realImage; // 持有真實圖片的引用std::string fileName;// 懶加載:需要時才創建真實圖片void ensureRealImageExists() {if (!realImage) {realImage = new RealImage(fileName);}}public:ImageProxy(const std::string& name) : realImage(nullptr), fileName(name) {std::cout << "創建圖片代理: " << fileName << " (未實際加載圖片)\n";}~ImageProxy() {delete realImage; // 代理負責管理真實對象的生命周期}void display() override {// 1. 記錄訪問日志(額外功能)std::cout << "[日志] 訪問圖片: " << fileName << "\n";// 2. 確保真實圖片已加載(延遲加載)ensureRealImageExists();// 3. 轉發請求給真實對象realImage->display();}std::string getFileName() const override {return fileName;}
};// 客戶端代碼:使用代理與使用真實對象完全一致
void clientCode(Image* image) {std::cout << "\n客戶端操作: " << image->getFileName() << "\n";image->display(); // 調用代理的display,實際會轉發給真實對象
}int main() {// 創建代理(此時不會加載真實圖片)Image* proxy1 = new ImageProxy("風景.jpg");Image* proxy2 = new ImageProxy("人物.png");// 第一次顯示圖片:觸發真實圖片加載clientCode(proxy1);// 第二次顯示同一圖片:直接使用已加載的真實圖片clientCode(proxy1);// 顯示另一張圖片clientCode(proxy2);// 清理資源delete proxy1;delete proxy2;return 0;
}
簡單來說,代理就像生活中的 “中介”—— 比如租房中介,它不直接提供房屋,但可以控制租客與房東的交互,并在過程中提供額外服務(篩選租客、簽訂合同等)。
核心角色:
- 抽象主題(Subject)
定義原始對象和代理對象共同遵循的接口,確保代理可被當作原始對象使用。 - 真實主題(Real Subject)
實際執行業務邏輯的原始對象,是代理的目標。 - 代理(Proxy)
實現抽象主題接口,內部持有真實主題的引用,負責在調用真實主題前后添加額外操作,并將請求轉發給真實主題。
適用場景:
- 延遲加載:當原始對象創建成本高時,代理可在需要時才初始化真實對象(如大型圖像加載)。
- 權限控制:代理可檢查調用者權限,只允許授權者訪問真實對象。
- 緩存:代理可緩存真實對象的計算結果,避免重復計算。
- 日志記錄:代理可記錄對真實對象的調用信息(如調用時間、參數等)。
C++ 代碼示例
下面以 “圖片查看器” 為例,展示代理模式的實現。當加載大型圖片時,使用代理實現延遲加載和日志記錄功能。
- Image:抽象主題接口,定義了
display()
和getFileName()
方法,保證代理和真實對象的接口一致性。 - RealImage:真實主題,代表需要加載的大型圖片。其構造函數會調用
loadFromDisk()
模擬耗時的加載過程。 - ImageProxy:代理類,核心功能包括:
- 延遲加載:僅在第一次調用
display()
時才創建RealImage
對象,避免不必要的資源消耗。 - 日志記錄:在轉發請求前添加訪問日志功能。
- 生命周期管理:代理負責創建和銷毀真實對象,客戶端無需關心。
- 延遲加載:僅在第一次調用
- 客戶端代碼:僅依賴
Image
接口,完全不知道代理的存在,符合 “依賴倒置原則”。
運行結果
創建圖片代理: 風景.jpg (未實際加載圖片)
創建圖片代理: 人物.png (未實際加載圖片)客戶端操作: 風景.jpg
[日志] 訪問圖片: 風景.jpg
正在從磁盤加載圖片: 風景.jpg (耗時操作)...
顯示圖片: 風景.jpg客戶端操作: 風景.jpg
[日志] 訪問圖片: 風景.jpg
顯示圖片: 風景.jpg客戶端操作: 人物.png
[日志] 訪問圖片: 人物.png
正在從磁盤加載圖片: 人物.png (耗時操作)...
顯示圖片: 人物.png
代理模式的優點
- 增強控制:可在不修改原始對象的情況下添加額外功能。
- 優化性能:通過延遲加載減少不必要的資源消耗。
- 符合開閉原則:新增代理不影響原始對象和客戶端代碼。
- 隔離性:客戶端無需了解原始對象的實現細節,只需與代理交互。
常見的代理類型還包括遠程代理(控制遠程對象訪問)、保護代理(權限控制)、智能引用代理(自動釋放資源)等,核心思想都是通過代理對象間接訪問原始對象并增強其功能。
八、觀察者模式
8.1 回答重點
**大話設計模式:**觀察者模式(發布-訂閱模式)屬于行為型模式,定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽一個主題對象,當主題對象的狀態發生變化時,所有依賴于它的觀察者都得到通知并被自動更新。
基本結構:
-
主題Subject
, 一般會定義成一個接口,提供方法用于注冊、刪除和通知觀察者,通常也包含一個狀態,當狀態發生改變時,通知所有的觀察者。 -
觀察者Observer
: 觀察者也需要實現一個接口,包含一個更新方法,在接收主題通知時執行對應的操作。 -
具體主題ConcreteSubject
: 主題的具體實現, 維護一個觀察者列表,包含了觀察者的注冊、刪除和通知方法。 -
具體觀察者ConcreteObserver
: 觀察者接口的具體實現,每個具體觀察者都注冊到具體主題中,當主題狀態變化并通知到具體觀察者,具體觀察者進行處理。
應用場景:
觀察者模式特別適用于一個對象的狀態變化會影響到其他對象,并且希望這些對象在狀態變化時能夠自動更新的情況。 比如說在圖形用戶界面中,按鈕、滑動條等組件的狀態變化可能需要通知其他組件更新,這使得觀察者模式被廣泛應用于GUI框架,比如Java的Swing框架。
此外,觀察者模式在前端開發和分布式系統中也有應用,比較典型的例子是前端框架Vue
, 當數據發生變化時,視圖會自動更新。而在分布式系統中,觀察者模式可以用于實現節點之間的消息通知機制,節點的狀態變化將通知其他相關節點。
示例代碼:
// 觀察者接口class Observer {
public:virtual void update(int hour) = 0;virtual ~Observer() = default; // 添加虛析構函數
};// 主題接口class Subject {
public:virtual void registerObserver(Observer* observer) = 0;virtual void removeObserver(Observer* observer) = 0;virtual void notifyObservers() = 0;virtual ~Subject() = default; // 添加虛析構函數
};// 具體主題實現class Clock : public Subject {
private:std::vector<Observer*> observers;int hour;public:Clock() : hour(0) {}void registerObserver(Observer* observer) override {observers.push_back(observer);}void removeObserver(Observer* observer) override {auto it = std::find(observers.begin(), observers.end(), observer);if (it != observers.end()) {observers.erase(it);}}void notifyObservers() override {for (Observer* observer : observers) {observer->update(hour);}}// 添加獲取觀察者的函數const std::vector<Observer*>& getObservers() const {return observers;}void tick() {hour = (hour + 1) % 24; // 模擬時間的推移notifyObservers();}
};// 具體觀察者實現class Student : public Observer {
private:std::string name;public:Student(const std::string& name) : name(name) {}void update(int hour) override {std::cout << name << " " << hour << std::endl;}
};int main() {// 讀取學生數量int N;std::cin >> N;// 創建時鐘Clock clock;// 注冊學生觀察者for (int i = 0; i < N; i++) {std::string studentName;std::cin >> studentName;clock.registerObserver(new Student(studentName));}// 讀取時鐘更新次數int updates;std::cin >> updates;// 模擬時鐘每隔一個小時更新一次for (int i = 0; i < updates; i++) {clock.tick();}for (Observer* observer : clock.getObservers()) {delete observer;}return 0;
}
8.2 擴展知識
觀察者模式(Observer Pattern)是一種行為型設計模式,它定義了對象之間的一對多依賴關系:當一個對象(被觀察者)的狀態發生變化時,所有依賴它的對象(觀察者)都會收到通知并自動更新。
這種模式就像訂閱報紙 —— 你(觀察者)訂閱了報紙后,每當有新報紙出版(被觀察者狀態變化),報社就會收到報紙(自動更新),無需你主動去詢問。
核心角色:
- Subject(被觀察者 / 主題)
維護一個觀察者列表,提供添加、刪除觀察者的方法,以及通知所有觀察者的方法。 - Observer(觀察者)
定義一個更新接口,當被觀察者狀態變化時,會調用此接口接收通知并更新自己。 - ConcreteSubject(具體被觀察者)
實際的被觀察者,當狀態變化時,觸發通知方法。 - ConcreteObserver(具體觀察者)
實現觀察者接口,定義收到通知后的具體處理邏輯。
適用場景:
- 當一個對象的變化需要聯動影響多個其他對象時(如 GUI 中的事件響應)。
- 當需要降低對象間耦合度,讓它們可以獨立變化時。
- 當一個對象必須通知其他對象,但又不知道具體有多少對象需要通知時。
C++ 代碼示例:
下面以 “氣象站” 為例:氣象站(被觀察者)會定期更新溫度,多個顯示設備(觀察者)需要實時顯示最新溫度。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>// 2. 觀察者接口
class Observer {
public:virtual ~Observer() = default;// 接收更新的接口(參數為被觀察者傳遞的狀態)virtual void update(float temperature) = 0;
};// 1. 被觀察者接口(主題)
class Subject {
public:virtual ~Subject() = default;// 添加觀察者virtual void attach(Observer* observer) = 0;// 移除觀察者virtual void detach(Observer* observer) = 0;// 通知所有觀察者virtual void notify() = 0;
};// 3. 具體被觀察者:氣象站
class WeatherStation : public Subject {
private:std::vector<Observer*> observers; // 觀察者列表float currentTemperature; // 當前溫度(被觀察的狀態)public:void setTemperature(float temp) {currentTemperature = temp;notify(); // 溫度變化時通知所有觀察者}// 添加觀察者void attach(Observer* observer) override {observers.push_back(observer);}// 移除觀察者void detach(Observer* observer) override {auto it = std::find(observers.begin(), observers.end(), observer);if (it != observers.end()) {observers.erase(it);}}// 通知所有觀察者void notify() override {for (Observer* observer : observers) {observer->update(currentTemperature); // 傳遞當前溫度}}
};// 4. 具體觀察者1:手機顯示
class PhoneDisplay : public Observer {
private:std::string name;public:PhoneDisplay(const std::string& n) : name(n) {}void update(float temperature) override {std::cout << "[" << name << "] 手機顯示 - 當前溫度: " << temperature << "°C\n";}
};// 4. 具體觀察者2:電腦顯示
class ComputerDisplay : public Observer {
public:void update(float temperature) override {std::cout << "[電腦顯示] 溫度更新: " << temperature << "°C,已記錄到日志\n";}
};// 客戶端代碼
int main() {// 創建被觀察者:氣象站WeatherStation weatherStation;// 創建觀察者:兩個手機顯示和一個電腦顯示PhoneDisplay phone1("小明的手機");PhoneDisplay phone2("小紅的手機");ComputerDisplay computer;// 注冊觀察者(訂閱氣象站)weatherStation.attach(&phone1);weatherStation.attach(&phone2);weatherStation.attach(&computer);// 氣象站溫度變化(觸發通知)std::cout << "=== 溫度更新為25°C ===" << std::endl;weatherStation.setTemperature(25.0f);// 移除一個觀察者(取消訂閱)weatherStation.detach(&phone2);std::cout << "\n=== 溫度更新為30°C ===" << std::endl;weatherStation.setTemperature(30.0f);return 0;
}
代碼說明
- Observer(觀察者接口):定義了
update()
方法,所有觀察者都必須實現此接口以接收通知。 - Subject(被觀察者接口):定義了
attach()
(添加)、detach()
(移除)和notify()
(通知)三個核心方法,規范被觀察者的行為。 - WeatherStation(具體被觀察者):
- 維護一個觀察者列表
observers
。 - 當
setTemperature()
被調用(狀態變化)時,立即調用notify()
通知所有觀察者。 - 實現了添加 / 移除觀察者的邏輯。
- 維護一個觀察者列表
- 具體觀察者:
PhoneDisplay
和ComputerDisplay
分別實現了update()
方法,定義了收到溫度更新后的具體行為(顯示方式不同)。- 觀察者無需知道被觀察者的具體類型,只需通過接口接收通知。
運行結果
=== 溫度更新為25°C ===
[小明的手機] 手機顯示 - 當前溫度: 25°C
[小紅的手機] 手機顯示 - 當前溫度: 25°C
[電腦顯示] 溫度更新: 25°C,已記錄到日志=== 溫度更新為30°C ===
[小明的手機] 手機顯示 - 當前溫度: 30°C
[電腦顯示] 溫度更新: 30°C,已記錄到日志
可以看到,當氣象站溫度變化時:
- 所有注冊的觀察者都會收到通知并更新。
- 移除觀察者(小紅的手機)后,它不再收到通知。
觀察者模式的優點
- 松耦合:被觀察者和觀察者相互獨立,一方變化不影響另一方。
- 可擴展性:新增觀察者無需修改被觀察者代碼(符合開閉原則)。
- 聯動性:輕松實現一個對象變化帶動多個對象自動更新的場景。
這種模式在 GUI 框架(如按鈕點擊事件)、消息訂閱系統、數據監控系統等場景中被廣泛使用。
九、策略模式
9.1 回答重點
**大話設計模式:**策略模式(strategy)它定義了算法家族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化,不會影響到使用算法的客戶。
策略模式是一種行為型設計模式,它定義了一系列算法(這些算法完成的是相同的工作,只是實現不同),并將每個算法封裝起來,使它們可以相互替換,而且算法的變化不會影響使用算法的客戶。
基本結構:
-
策略類
Strategy
: 定義所有支持的算法的公共接口。 -
具體策略類
ConcreteStrategy
: 實現了策略接口,提供具體的算法實現。 -
上下文類
Context
: 包含一個策略實例,并在需要時調用策略對象的方法。
應用場景:
舉個例子,電商網站對于商品的折扣策略有不同的算法,比如新用戶滿減優惠,不同等級會員的打折情況不同,這種情況下會產生大量的if-else語句
, 并且如果優惠政策修改時,還需要修改原來的代碼,不符合開閉原則。
這就可以將不同的優惠算法封裝成獨立的類來避免大量的條件語句,如果新增優惠算法,可以添加新的策略類來實現,客戶端在運行時選擇不同的具體策略,而不必修改客戶端代碼改變優惠策略。
示例代碼:
// 抽象購物優惠策略接口
class DiscountStrategy {
public:virtual int applyDiscount(int originalPrice) = 0;virtual ~DiscountStrategy() = default; // 添加虛析構函數
};// 九折優惠策略
class DiscountStrategy1 : public DiscountStrategy {
public:int applyDiscount(int originalPrice) override {return static_cast<int>(std::round(originalPrice * 0.9));}
};// 滿減優惠策略
class DiscountStrategy2 : public DiscountStrategy {
private:int thresholds[4] = {100, 150, 200, 300};int discounts[4] = {5, 15, 25, 40};public:int applyDiscount(int originalPrice) override {for (int i = sizeof(thresholds) / sizeof(thresholds[0]) - 1; i >= 0; i--) {if (originalPrice >= thresholds[i]) {return originalPrice - discounts[i];}}return originalPrice;}
};// 上下文類
class DiscountContext {
private:DiscountStrategy* discountStrategy;public:void setDiscountStrategy(DiscountStrategy* discountStrategy) {this->discountStrategy = discountStrategy;}int applyDiscount(int originalPrice) {return discountStrategy->applyDiscount(originalPrice);}
};int main() {// 讀取需要計算優惠的次數int N;std::cin >> N;std::cin.ignore(); // 忽略換行符for (int i = 0; i < N; i++) {// 讀取商品價格和優惠策略int M, strategyType;std::cin >> M >> strategyType;// 根據優惠策略設置相應的打折策略DiscountStrategy* discountStrategy;switch (strategyType) {case 1:discountStrategy = new DiscountStrategy1();break;case 2:discountStrategy = new DiscountStrategy2();break;default:// 處理未知策略類型std::cout << "Unknown strategy type" << std::endl;return 1;}// 設置打折策略DiscountContext context;context.setDiscountStrategy(discountStrategy);// 應用打折策略并輸出優惠后的價格int discountedPrice = context.applyDiscount(M);std::cout << discountedPrice << std::endl;// 釋放動態分配的打折策略對象delete discountStrategy;delete discountStrategy;}return 0;
}
9.2 擴展知識
策略模式(Strategy Pattern)是一種行為型設計模式,它定義了一系列算法,將每個算法封裝起來并使它們可以相互替換。這種模式讓算法的變化獨立于使用算法的客戶端,從而實現靈活的算法切換。
簡單來說,策略模式就像手機的拍照模式 —— 你可以在 “普通模式”、“夜景模式”、“人像模式” 之間切換,每種模式都是一種拍照策略,而手機相機就是使用這些策略的客戶端。
核心角色:
- Strategy(策略接口)
定義所有支持的算法的公共接口,客戶端通過這個接口調用具體策略的算法。 - ConcreteStrategy(具體策略)
實現策略接口,包含具體的算法邏輯。 - Context(上下文)
持有一個策略對象的引用,負責將客戶端的請求委派給當前的策略對象執行,同時提供切換策略的方法。
適用場景:
- 當一個問題有多種解決方法,且需要在運行時動態選擇其中一種時。
- 當一個類中包含多種條件判斷(如大量
if-else
或switch
),且這些判斷對應不同算法時(可用策略模式消除條件判斷)。 - 當需要隱藏算法的實現細節,只暴露其使用接口時。
C++ 代碼示例:
下面以 “支付系統” 為例:一個電商平臺支持多種支付方式(支付寶、微信支付、銀行卡支付),使用策略模式實現支付方式的靈活切換
#include <iostream>
#include <string>// 1. 策略接口:支付策略
class PaymentStrategy {
public:virtual ~PaymentStrategy() = default;// 支付接口(參數為金額)virtual void pay(double amount) const = 0;
};// 2. 具體策略1:支付寶支付
class AlipayStrategy : public PaymentStrategy {
private:std::string username; // 支付寶賬號public:AlipayStrategy(const std::string& user) : username(user) {}void pay(double amount) const override {std::cout << "使用支付寶支付 " << amount << " 元,賬號:" << username << std::endl;}
};// 2. 具體策略2:微信支付
class WechatPayStrategy : public PaymentStrategy {
private:std::string openId; // 微信openIdpublic:WechatPayStrategy(const std::string& id) : openId(id) {}void pay(double amount) const override {std::cout << "使用微信支付 " << amount << " 元,OpenId:" << openId << std::endl;}
};// 2. 具體策略3:銀行卡支付
class BankCardStrategy : public PaymentStrategy {
private:std::string cardNumber; // 卡號std::string name; // 持卡人姓名public:BankCardStrategy(const std::string& card, const std::string& n) : cardNumber(card), name(n) {}void pay(double amount) const override {std::cout << "使用銀行卡支付 " << amount << " 元,卡號:" << cardNumber << ",持卡人:" << name << std::endl;}
};// 3. 上下文:訂單支付
class Order {
private:PaymentStrategy* paymentStrategy; // 當前支付策略double totalAmount; // 訂單總金額public:Order(double amount) : paymentStrategy(nullptr), totalAmount(amount) {}~Order() {// 清理策略對象delete paymentStrategy;}// 設置支付策略(切換策略)void setPaymentStrategy(PaymentStrategy* strategy) {// 先刪除舊策略delete paymentStrategy;paymentStrategy = strategy;}// 執行支付(委托給當前策略)void pay() const {if (paymentStrategy) {paymentStrategy->pay(totalAmount);} else {std::cout << "請先選擇支付方式!" << std::endl;}}
};// 客戶端代碼
int main() {// 創建一個100元的訂單Order order(100.0);std::cout << "訂單金額:100元\n" << std::endl;// 選擇支付寶支付order.setPaymentStrategy(new AlipayStrategy("user123@alipay.com"));order.pay();// 切換為微信支付order.setPaymentStrategy(new WechatPayStrategy("o6_bmjrPTlm6_2sgVt7hMZOPfL2M"));order.pay();// 切換為銀行卡支付order.setPaymentStrategy(new BankCardStrategy("6222 **** **** 1234", "張三"));order.pay();return 0;
}
十、備忘錄模式
10.1 回答重點
大話設計模式: 備忘錄(Memento),在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態。這樣以后可以將該對象恢復到原來保存的狀態。
備忘錄模式(Memento Pattern)是一種行為型設計模式,它允許在不暴露對象實現的情況下捕獲對象的內部狀態并在對象之外保存這個狀態,以便稍后可以將其還原到先前的狀態。
基本結構:
備忘錄模式包括以下幾個重要角色:
-
發起人
Originator
: 需要還原狀態的那個對象,負責創建一個【備忘錄】,并使用備忘錄記錄當前時刻的內部狀態。 -
備忘錄
Memento
: 存儲發起人對象的內部狀態,它可以包含發起人的部分或全部狀態信息,但是對外部是不可見的,只有發起人能夠訪問備忘錄對象的狀態。(備忘錄有兩個接口,發起人能夠通過寬接口訪問數據,管理者只能看到窄接口,并將備忘錄傳遞給其他對象。) -
管理者
Caretaker
: 負責存儲備忘錄對象,但并不了解其內部結構,管理者可以存儲多個備忘錄對象。 -
客戶端:在需要恢復狀態時,客戶端可以從管理者那里獲取備忘錄對象,并將其傳遞給發起人進行狀態的恢復。
應用場景:
備忘錄模式在保證了對象內部狀態的封裝和私有性前提下可以輕松地添加新的備忘錄和發起人,實現“備份”,不過 備份對象往往會消耗較多的內存,資源消耗增加。
備忘錄模式常常用來實現撤銷和重做功能,比如在Java Swing GUI編程中,javax.swing.undo
包中的撤銷(undo)和重做(redo)機制使用了備忘錄模式。UndoManager
和UndoableEdit
接口是與備忘錄模式相關的主要類和接口。
示例代碼:
// 備忘錄
class Memento {
private:int value;public:Memento(int val) : value(val) {}int getValue() const {return value;}
};// 發起人(Originator)
class Counter {
private:int value;std::stack<Memento> undoStack;std::stack<Memento> redoStack;public:void increment() {redoStack = std::stack<Memento>(); // 清空 redoStackundoStack.push(Memento(value));value++;}void decrement() {redoStack = std::stack<Memento>(); // 清空 redoStackundoStack.push(Memento(value));value--;}void undo() {if (!undoStack.empty()) {redoStack.push(Memento(value));value = undoStack.top().getValue();undoStack.pop();}}void redo() {if (!redoStack.empty()) {undoStack.push(Memento(value));value = redoStack.top().getValue();redoStack.pop();}}int getValue() const {return value;}
};int main() {Counter counter;// 處理計數器應用的輸入std::string operation;while (std::cin >> operation) {if (operation == "Increment") {counter.increment();} else if (operation == "Decrement") {counter.decrement();} else if (operation == "Undo") {counter.undo();} else if (operation == "Redo") {counter.redo();}// 輸出當前計數器的值std::cout << counter.getValue() << std::endl;}return 0;
}
10.2 擴展知識
備忘錄模式(Memento Pattern)是一種行為型設計模式,用于捕獲對象在某一時刻的內部狀態,并將其保存起來,以便在將來需要時恢復該狀態。這種模式就像游戲中的 “存檔” 功能,允許你在關鍵時刻保存進度,之后可以隨時回到該狀態。
核心角色:
- Originator(發起人)
負責創建一個備忘錄(Memento),用于記錄自身當前的內部狀態,同時也能從備忘錄恢復狀態。 - Memento(備忘錄)
存儲發起人的內部狀態,且僅允許發起人訪問其內容(對其他對象隱藏細節)。 - Caretaker(管理者)
負責保存備忘錄,但不能對備忘錄的內容進行操作或檢查(僅負責存儲和檢索)。
適用場景:
- 需要保存和恢復對象狀態的場景(如編輯器的撤銷操作、游戲存檔)。
- 不希望暴露對象內部狀態,卻需要捕獲和恢復狀態的場景。
- 狀態變化頻繁,且需要回滾到歷史狀態的場景。
C++ 代碼示例:
下面以 “文本編輯器” 為例,實現撤銷(Undo)功能,展示備忘錄模式的用法:
#include <iostream>
#include <string>
#include <vector>// 2. 備忘錄:存儲文本編輯器的狀態
class EditorMemento {
private:// 只有發起人才可以訪問備忘錄的狀態(友元類)friend class TextEditor;std::string content; // 保存的文本內容// 私有構造函數,防止外部創建(只能由發起人創建)EditorMemento(const std::string& cnt) : content(cnt) {}// 獲取保存的內容(僅發起人可調用)std::string getContent() const {return content;}
};// 1. 發起人:文本編輯器
class TextEditor {
private:std::string content; // 當前文本內容public:// 設置文本內容void setContent(const std::string& cnt) {content = cnt;}// 獲取當前文本內容std::string getContent() const {return content;}// 創建備忘錄(保存當前狀態)EditorMemento* save() {return new EditorMemento(content); //EditorMemento的構造函數不是一看私有的嗎?怎么回成功調用呢?====>friend}// 從備忘錄恢復狀態void restore(EditorMemento* memento) {if (memento) {content = memento->getContent();}}
};// 3. 管理者:負責保存和管理備忘錄(實現撤銷歷史)
class History {
private:std::vector<EditorMemento*> mementos; // 存儲備忘錄的容器public:~History() {// 清理所有備忘錄for (auto m : mementos) {delete m;}mementos.clear();}// 保存新的備忘錄void push(EditorMemento* memento) {mementos.push_back(memento);}// 獲取最近的備忘錄(用于撤銷操作)EditorMemento* pop() {if (mementos.empty()) {return nullptr;}EditorMemento* last = mementos.back();mementos.pop_back();return last;}
};// 客戶端代碼
int main() {// 創建文本編輯器和歷史記錄管理器TextEditor editor;History history;// 操作1:輸入第一段文本并保存狀態editor.setContent("第一段文本");std::cout << "當前內容: " << editor.getContent() << "\n";history.push(editor.save()); // 存檔// 操作2:輸入第二段文本并保存狀態editor.setContent("第二段文本");std::cout << "當前內容: " << editor.getContent() << "\n";history.push(editor.save()); // 存檔// 操作3:輸入第三段文本(不保存)editor.setContent("第三段文本");std::cout << "當前內容: " << editor.getContent() << "\n";// 撤銷1:回到上一個狀態editor.restore(history.pop());std::cout << "撤銷后內容: " << editor.getContent() << "\n";// 撤銷2:回到更早的狀態editor.restore(history.pop());std::cout << "再次撤銷后內容: " << editor.getContent() << "\n";return 0;
}
代碼說明:
- PaymentStrategy(策略接口):定義了支付的統一接口
pay()
,所有支付方式都必須實現這個接口。 - 具體策略類:
AlipayStrategy
、WechatPayStrategy
、BankCardStrategy
分別實現了不同支付方式的具體邏輯。- 每個策略類都封裝了對應支付方式所需的參數(如賬號、卡號)和支付流程。
- Order(上下文):
- 持有一個
PaymentStrategy
指針,代表當前使用的支付策略。 - 提供
setPaymentStrategy()
方法,允許在運行時動態切換支付方式。 pay()
方法將支付請求委派給當前策略對象執行,自身不包含具體支付邏輯。
- 持有一個
運行結果:
訂單金額:100元使用支付寶支付 100 元,賬號:user123@alipay.com
使用微信支付 100 元,OpenId:o6_bmjrPTlm6_2sgVt7hMZOPfL2M
使用銀行卡支付 100 元,卡號:6222 **** **** 1234,持卡人:張三
策略模式的優點:
- 消除條件判斷:用策略切換替代
if-else
或switch
語句,使代碼更清晰。 - 增強擴展性:新增策略只需實現策略接口,無需修改原有代碼(符合開閉原則)。
- 算法復用:不同場景可復用相同的策略實現。
- 靈活性高:可在運行時動態切換算法,適應不同需求。
策略模式特別適合在需要頻繁切換算法、或算法邏輯復雜且多變的場景中使用,例如排序算法、加密算法、支付方式等場景。
十一、模板模式
11.1 回答重點
**大話設計模式:**模板方法模式(Template Method Pattern)是一種行為型設計模式, 它定義了一個算法的骨架,將一些步驟的實現延遲到子類。模板方法模式使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟。
基本結構:
-
模板類
AbstractClass
:由一個模板方法和若干個基本方法構成,模板方法定義了邏輯的骨架,按照順序調用包含的基本方法,基本方法通常是一些抽象方法,這些方法由子類去實現。基本方法還包含一些具體方法,它們是算法的一部分但已經有默認實現,在具體子類中可以繼承或者重寫。 -
具體類
ConcreteClass
:繼承自模板類,實現了在模板類中定義的抽象方法,以完成算法中特定步驟的具體實現。
應用場景:
-
模板方法模式將算法的不變部分被封裝在模板方法中,而可變部分算法由子類繼承實現,這樣做可以很好的提高代碼的復用性,但是當算法的框架發生變化時,可能需要修改模板類,這也會影響到所有的子類。
-
總體來說,當算法的整體步驟很固定,但是個別步驟在更詳細的層次上的實現可能不同時,通常考慮模板方法模式來處理。在已有的工具和庫中, Spring框架中的
JdbcTemplate
類使用了模板方法模式,其中定義了一些執行數據庫操作的模板方法,具體的數據庫操作由回調函數提供。而在Java的JDK源碼中,AbstractList
類也使用了模板方法模式,它提供了一些通用的方法,其中包括一些模板方法。具體的列表操作由子類實現。
示例代碼:
// 抽象類
class CoffeeMakerTemplate {
private:std::string coffeeName;public:// 構造函數,接受咖啡名稱參數CoffeeMakerTemplate(const std::string& coffeeName) :coffeeName(coffeeName){};// 模板方法定義咖啡制作過程virtual void makeCoffee() {std::cout << "Making " << coffeeName << ":\n";grindCoffeeBeans();brewCoffee();addCondiments();std::cout << '\n';}// 具體步驟的具體實現由子類提供virtual void grindCoffeeBeans() = 0;virtual void brewCoffee() = 0;// 添加調料的默認實現virtual void addCondiments() {std::cout << "Adding condiments\n";}
};// 具體的美式咖啡類
class AmericanCoffeeMaker : public CoffeeMakerTemplate {
public:
// 構造函數傳遞咖啡名稱AmericanCoffeeMaker() : CoffeeMakerTemplate("American Coffee") {}void grindCoffeeBeans() override {std::cout << "Grinding coffee beans\n";}void brewCoffee() override {std::cout << "Brewing coffee\n";}
};// 具體的拿鐵咖啡類
class LatteCoffeeMaker : public CoffeeMakerTemplate {
public:
// 構造函數傳遞咖啡名稱LatteCoffeeMaker() : CoffeeMakerTemplate("Latte") {}void grindCoffeeBeans() override {std::cout << "Grinding coffee beans\n";}void brewCoffee() override {std::cout << "Brewing coffee\n";}// 添加調料的特定實現void addCondiments() override {std::cout << "Adding milk\n";std::cout << "Adding condiments\n";}
};int main() {std::unique_ptr<CoffeeMakerTemplate> coffeeMaker;int coffeeType;while (std::cin >> coffeeType) {if (coffeeType == 1) {coffeeMaker = std::make_unique<AmericanCoffeeMaker>();} else if (coffeeType == 2) {coffeeMaker = std::make_unique<LatteCoffeeMaker>();} else {std::cout << "Invalid coffee type\n";continue;}// 制作咖啡coffeeMaker->makeCoffee();}return 0;
}
11.2 擴展知識
模板模式(Template Pattern)是一種行為型設計模式,它定義了一個算法的骨架流程,將某些步驟的具體實現延遲到子類中。這樣可以在不改變算法整體結構的情況下,讓子類重寫特定步驟,實現算法細節的定制化。
簡單來說,模板模式就像制作蛋糕的食譜:食譜規定了 “準備材料→混合→烘烤→裝飾” 的固定步驟(骨架),但具體用什么材料、如何裝飾可以由不同的廚師(子類)自行實現。
核心角色:
- 抽象類(Abstract Class)
定義算法的骨架流程(模板方法),其中包含多個抽象步驟(由子類實現)和可能的具體步驟(通用實現)。 - 具體子類(Concrete Class)
繼承抽象類,實現模板方法中定義的抽象步驟,完成算法的具體細節。
關鍵特點:
- 模板方法(Template Method):在抽象類中定義,是算法的骨架,按順序調用各個步驟方法。
- 鉤子方法(Hook Method):抽象類中定義的可選方法(通常有默認實現),子類可重寫以影響模板方法的流程。
- 不可變骨架:算法的整體結構由抽象類控制,子類不能改變流程順序,只能定制步驟實現。
C++ 代碼示例
下面以 “制作飲料” 為例:沖咖啡和泡茶的流程相似(都需要 “煮水→沖泡→倒入杯子→添加調料”),但具體步驟實現不同,用模板模式統一流程并區分細節。
#include <iostream>
#include <string>// 1. 抽象類:定義飲料制作的模板流程
class Beverage {
public:// 修正:將模板方法聲明為虛函數,再用final修飾virtual void prepareRecipe() final { // 此處添加virtual關鍵字boilWater(); // 步驟1:煮水(通用步驟)brew(); // 步驟2:沖泡(抽象步驟,子類實現)pourInCup(); // 步驟3:倒入杯子(通用步驟)if (customerWantsCondiments()) { // 鉤子方法:判斷是否需要調料addCondiments(); // 步驟4:添加調料(抽象步驟,子類實現)}}// 抽象方法:沖泡(子類必須實現)virtual void brew() = 0;// 抽象方法:添加調料(子類必須實現)virtual void addCondiments() = 0;// 具體方法:煮水(通用實現)void boilWater() {std::cout << "煮水至沸騰\n";}// 具體方法:倒入杯子(通用實現)void pourInCup() {std::cout << "倒入杯子中\n";}// 鉤子方法:是否需要添加調料(默認返回true,子類可重寫)virtual bool customerWantsCondiments() {return true;}virtual ~Beverage() = default; // 虛析構函數
};// 2. 具體子類:咖啡
class Coffee : public Beverage {
public:// 實現咖啡的沖泡方式void brew() override {std::cout << "用沸水沖泡咖啡粉\n";}// 實現咖啡的調料添加void addCondiments() override {std::cout << "添加糖和牛奶\n";}// 重寫鉤子方法:詢問用戶是否需要調料bool customerWantsCondiments() override {std::string answer;std::cout << "請問需要給咖啡加奶和糖嗎?(y/n): ";std::cin >> answer;return answer == "y" || answer == "Y";}
};// 2. 具體子類:茶
class Tea : public Beverage {
public:// 實現茶的沖泡方式void brew() override {std::cout << "用沸水浸泡茶葉\n";}// 實現茶的調料添加void addCondiments() override {std::cout << "添加檸檬\n";}
};// 客戶端代碼
int main() {std::cout << "=== 制作咖啡 ===" << std::endl;Beverage* coffee = new Coffee();coffee->prepareRecipe();std::cout << "\n=== 制作茶 ===" << std::endl;Beverage* tea = new Tea();tea->prepareRecipe();// 清理資源delete coffee;delete tea;return 0;
}
代碼說明:
- Beverage(抽象類):
prepareRecipe()
是模板方法(用final
修飾,防止子類重寫),定義了 “煮水→沖泡→倒入杯子→加調料” 的固定流程。- 包含兩個抽象方法
brew()
和addCondiments()
,由子類實現具體細節。 - 包含通用方法
boilWater()
和pourInCup()
,所有飲料制作都共用這些步驟。 - 鉤子方法
customerWantsCondiments()
提供默認實現(返回true
),子類可重寫以改變流程(如咖啡詢問用戶是否加調料)。
- 具體子類:
Coffee
實現了咖啡特有的brew()
(沖泡咖啡粉)和addCondiments()
(加糖奶),并通過鉤子方法實現了交互邏輯。Tea
實現了茶特有的brew()
(浸泡茶葉)和addCondiments()
(加檸檬),使用默認鉤子方法(總是加調料)。
運行結果:
=== 制作咖啡 ===
煮水至沸騰
用沸水沖泡咖啡粉
倒入杯子中
請問需要給咖啡加奶和糖嗎?(y/n): y
添加糖和牛奶=== 制作茶 ===
煮水至沸騰
用沸水浸泡茶葉
倒入杯子中
添加檸檬
模板模式的優點:
- 代碼復用:將通用流程和步驟放在抽象類中,避免重復代碼。
- 控制反轉:抽象類定義骨架,子類填充細節,符合 “開閉原則”(擴展容易,修改骨架難)。
- 流程固化:確保算法的關鍵步驟和執行順序不被改變,只允許定制具體實現。
這種模式在框架設計中非常常見,例如:
- 測試框架中,
setUp()
→runTest()
→tearDown()
的測試流程。 - GUI 框架中,事件處理的初始化→處理→清理流程。
- 數據庫訪問的連接→查詢→關閉流程。
通過模板模式,可以平衡 “流程標準化” 和 “細節定制化” 的需求,是代碼設計中實現復用和擴展的重要手段。
十二、狀態模式
12.1 回答重點
**大話設計模式:**狀態模式(State),當一個對象的內在狀態改變時允許改變其行為,這個對象看起來像是改變其類。
狀態模式(State Pattern):是一種行為型設計模式,它適用于一個對象在在不同的狀態下有不同的行為時,比如說電燈的開、關、閃爍是不停的狀態,狀態不同時,對應的行為也不同,在沒有狀態模式的情況下,為了添加新的狀態或修改現有的狀態,往往需要修改已有的代碼,這違背了開閉原則,而且如果對象的狀態切換邏輯和各個狀態的行為都在同一個類中實現,就可能導致該類的職責過重,不符合單一職責原則。而狀態模式將每個狀態的行為封裝在一個具體狀態類中,使得每個狀態類相對獨立,并將對象在不同狀態下的行為進行委托,從而使得對象的狀態可以在運行時動態改變,每個狀態的實現也不會影響其他狀態。
基本結構:
-
State
(狀態): 定義一個接口,用于封裝與Context的一個特定狀態相關的行為。 -
ConcreteState
(具體狀態): 負責處理Context在狀態改變時的行為, 每一個具體狀態子類實現一個與Context
的一個狀態相關的行為。 -
Context
(上下文): 維護一個具體狀態子類的實例,這個實例定義當前的狀態。
應用場景:
狀態模式將每個狀態的實現都封裝在一個類中,每個狀態類的實現相對獨立,使得添加新狀態或修改現有狀態變得更加容易,避免了使用大量的條件語句來控制對象的行為。但是如果狀態過多,會導致類的數量增加,可能會使得代碼結構復雜。
總的來說,狀態模式適用于有限狀態機的場景,其中對象的行為在運行時可以根據內部狀態的改變而改變,在游戲開發中,Unity3D 的 Animator 控制器就是一個狀態機。它允許開發人員定義不同的狀態(動畫狀態),并通過狀態轉換來實現角色的動畫控制和行為切換。
示例代碼:
// 狀態接口
class State {
public:virtual std::string handle() = 0; // 處理狀態的方法
};// 具體狀態類
class OnState : public State {
public:std::string handle() override {return "Light is ON";}
};class OffState : public State {
public:std::string handle() override {return "Light is OFF";}
};class BlinkState : public State {
public:std::string handle() override {return "Light is Blinking";}
};// 上下文類
class Light {
private:State* state; // 當前狀態public:Light() : state(new OffState()) {} // 初始狀態為關閉void setState(State* newState) { // 設置新的狀態delete state; // 釋放之前的狀態對象state = newState;}std::string performOperation() { // 執行當前狀態的操作return state->handle();}~Light() {delete state; // 釋放內存}
};int main() {// 讀取要輸入的命令數量int n;std::cin >> n;std::cin.ignore(); // 消耗掉整數后的換行符// 創建一個Light對象Light light;// 處理用戶輸入的每個命令for (int i = 0; i < n; i++) {// 讀取命令并去掉首尾空白字符std::string command;std::getline(std::cin, command);// 根據命令執行相應的操作if (command == "ON") {light.setState(new OnState());} else if (command == "OFF") {light.setState(new OffState());} else if (command == "BLINK") {light.setState(new BlinkState());} else {// 處理無效命令std::cout << "Invalid command: " << command << std::endl;}// 在每個命令后顯示燈的當前狀態std::cout << light.performOperation() << std::endl;}return 0;
}
12.2 擴展知識
狀態模式(State Pattern)是一種行為型設計模式,它允許對象在內部狀態改變時改變其行為,使對象看起來好像修改了它的類。這種模式通過將狀態相關的行為封裝到不同的狀態對象中,實現狀態與行為的綁定,以及狀態之間的靈活切換。
簡單來說,狀態模式就像交通信號燈:信號燈有紅、黃、綠三種狀態,每種狀態對應不同的行為(禁止通行、準備通行、允許通行),且狀態之間會按特定規則切換(紅→黃→綠→紅…)。
核心角色:
- Context(上下文)
維護一個當前狀態的引用,負責將狀態相關的請求委派給當前狀態對象處理,同時提供切換狀態的方法。 - State(狀態接口)
定義所有具體狀態的公共接口,聲明了上下文在特定狀態下應有的行為方法。 - ConcreteState(具體狀態)
實現狀態接口,包含該狀態下的具體行為邏輯,同時可能定義狀態轉換的條件和目標。
適用場景:
- 當一個對象的行為取決于其狀態,且在運行時需要根據狀態改變行為時(如訂單狀態、生命周期狀態)。
- 當一個類中包含大量與狀態相關的條件判斷(
if-else
或switch
),導致代碼臃腫難以維護時。 - 當狀態轉換規則復雜,需要集中管理不同狀態間的切換邏輯時。
C++ 代碼示例:
下面以 “訂單狀態管理” 為例:一個電商訂單有 “待支付”、“已支付”、“已發貨”、“已完成” 四種狀態,每種狀態下的操作(支付、發貨、確認收貨)有不同的行為和狀態轉換規則。
#include <iostream>
#include <string>// 前向聲明:上下文類
class Order;// 2. 狀態接口
class OrderState {
public:virtual ~OrderState() = default;// 聲明各種狀態下可能的行為virtual void pay(Order* order) = 0;virtual void ship(Order* order) = 0;virtual void confirm(Order* order) = 0;virtual std::string getStateName() = 0;
};// 1. 上下文:訂單
class Order {
private:OrderState* currentState; // 當前狀態std::string orderId;public:Order(const std::string& id);~Order();// 狀態轉換:切換到新狀態void setState(OrderState* newState);// 訂單操作(委派給當前狀態處理)void pay() { currentState->pay(this); }void ship() { currentState->ship(this); }void confirm() { currentState->confirm(this); }std::string getOrderId() const { return orderId; }
};// 3. 具體狀態1:待支付
class PendingPaymentState : public OrderState {
public:void pay(Order* order) override;void ship(Order* order) override {std::cout << "錯誤:訂單" << order->getOrderId() << "未支付,不能發貨!\n";}void confirm(Order* order) override {std::cout << "錯誤:訂單" << order->getOrderId() << "未支付,不能確認收貨!\n";}std::string getStateName() override { return "待支付"; }
};// 3. 具體狀態2:已支付
class PaidState : public OrderState {
public:void pay(Order* order) override {std::cout << "錯誤:訂單" << order->getOrderId() << "已支付,不能重復支付!\n";}void ship(Order* order) override;void confirm(Order* order) override {std::cout << "錯誤:訂單" << order->getOrderId() << "未發貨,不能確認收貨!\n";}std::string getStateName() override { return "已支付"; }
};// 3. 具體狀態3:已發貨
class ShippedState : public OrderState {
public:void pay(Order* order) override {std::cout << "錯誤:訂單" << order->getOrderId() << "已發貨,無需支付!\n";}void ship(Order* order) override {std::cout << "錯誤:訂單" << order->getOrderId() << "已發貨,不能重復發貨!\n";}void confirm(Order* order) override;std::string getStateName() override { return "已發貨"; }
};// 3. 具體狀態4:已完成
class CompletedState : public OrderState {
public:void pay(Order* order) override {std::cout << "錯誤:訂單" << order->getOrderId() << "已完成,無需支付!\n";}void ship(Order* order) override {std::cout << "錯誤:訂單" << order->getOrderId() << "已完成,無需發貨!\n";}void confirm(Order* order) override {std::cout << "錯誤:訂單" << order->getOrderId() << "已完成,不能重復確認!\n";}std::string getStateName() override { return "已完成"; }
};// 上下文類實現
Order::Order(const std::string& id) : orderId(id) {// 初始狀態為待支付currentState = new PendingPaymentState();std::cout << "創建訂單 " << orderId << ",當前狀態:" << currentState->getStateName() << "\n";
}Order::~Order() {delete currentState;
}void Order::setState(OrderState* newState) {delete currentState;currentState = newState;std::cout << "訂單" << orderId << "狀態變更為:" << currentState->getStateName() << "\n";
}// 具體狀態方法實現(狀態轉換邏輯)
void PendingPaymentState::pay(Order* order) {std::cout << "訂單" << order->getOrderId() << "支付成功!\n";order->setState(new PaidState()); // 轉換到已支付狀態
}void PaidState::ship(Order* order) {std::cout << "訂單" << order->getOrderId() << "發貨成功!\n";order->setState(new ShippedState()); // 轉換到已發貨狀態
}void ShippedState::confirm(Order* order) {std::cout << "訂單" << order->getOrderId() << "確認收貨成功!\n";order->setState(new CompletedState()); // 轉換到已完成狀態
}// 客戶端代碼
int main() {// 創建一個訂單(初始狀態:待支付)Order order("ORD-12345");// 執行一系列操作order.pay(); // 支付訂單(狀態變為已支付)order.ship(); // 發貨(狀態變為已發貨)order.confirm();// 確認收貨(狀態變為已完成)// 嘗試無效操作(已完成狀態下的支付)order.pay();return 0;
}
代碼說明:
- OrderState(狀態接口):
- 定義了訂單在任何狀態下可能的操作(
pay
、ship
、confirm
)。 - 所有具體狀態都必須實現這些方法,定義該狀態下的行為邏輯。
- 定義了訂單在任何狀態下可能的操作(
- Order(上下文):
- 持有當前狀態的引用(
currentState
),并將所有操作委派給當前狀態處理。 - 提供
setState()
方法用于切換狀態,封裝了狀態轉換的細節。
- 持有當前狀態的引用(
- 具體狀態類:
- 每個類對應一種訂單狀態(
PendingPaymentState
、PaidState
等)。 - 實現了該狀態下允許的操作(如
PendingPaymentState
中只有pay()
有效)和狀態轉換邏輯(如支付后切換到PaidState
)。 - 對無效操作(如未支付時發貨)提供錯誤提示。
- 每個類對應一種訂單狀態(
運行結果:
創建訂單 ORD-12345,當前狀態:待支付
訂單ORD-12345支付成功!
訂單ORD-12345狀態變更為:已支付
訂單ORD-12345發貨成功!
訂單ORD-12345狀態變更為:已發貨
訂單ORD-12345確認收貨成功!
訂單ORD-12345狀態變更為:已完成
錯誤:訂單ORD-12345已完成,無需支付!
狀態模式的優點:
- 消除龐大的條件判斷:將不同狀態的行為分散到不同類中,替代復雜的
if-else
或switch
語句。 - 狀態轉換清晰:每個狀態類自行控制狀態轉換的邏輯,職責單一,易于維護。
- 擴展性好:新增狀態只需添加新的狀態類,無需修改現有代碼(符合開閉原則)。
- 狀態封裝:狀態的細節對上下文和客戶端透明,只通過接口交互。
狀態模式特別適合實現有明確狀態劃分和狀態轉換規則的系統,如訂單系統、工作流引擎、游戲角色狀態等場景,能顯著提高代碼的可讀性和可維護性。
十三、 責任鏈模式
13.1 回答重點
**大話設計模式:**責任鏈模式(Chain of Responsibility)使得多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關系。將這個對象練成一條鏈,并且沿著這條鏈傳遞該請求,直到有一個對象處理它為止。
責任鏈模式是一種行為型設計模式,它允許你構建一個對象鏈,讓請求從鏈的一端進入,然后沿著鏈上的對象依次處理,直到鏈上的某個對象能夠處理該請求為止。
職責鏈上的處理者就是一個對象,可以對請求進行處理或者將請求轉發給下一個節點,這個場景在生活中很常見,就是一個逐層向上遞交的過程,最終的請求要么被處理者所處理,要么處理不了,這也因此可能導致請求無法被處理。
基本結構:
-
處理者
Handler
:定義一個處理請求的接口,包含一個處理請求的抽象方法和一個指向下一個處理者的鏈接。 -
具體處理者
ConcreteHandler
: 實現處理請求的方法,并判斷能否處理請求,如果能夠處理請求則進行處理,否則將請求傳遞給下一個處理者。 -
客戶端:創建并組裝處理者對象鏈,并將請求發送到鏈上的第一個處理者。
應用場景:
責任鏈模式具有下面幾個優點:
-
降低耦合度:將請求的發送者和接收者解耦,每個具體處理者都只負責處理與自己相關的請求,客戶端不需要知道具體是哪個處理者處理請求。
-
增強靈活性:可以動態地添加或刪除處理者,改變處理者之間的順序以滿足不同需求。
但是由于一個請求可能會經過多個處理者,這可能會導致一些性能問題,并且如果整個鏈上也沒有合適的處理者來處理請求,就會導致請求無法被處理。
責任鏈模式是設計模式中簡單且常見的設計模式,在日常中也會經常使用到,比如Java開發中過濾器的鏈式處理,以及Spring框架中的攔截器,都組裝成一個處理鏈對請求、響應進行處理。
示例代碼:
class LeaveHandler {
public:virtual void handleRequest(const std::string& name, int days) = 0;
};class Supervisor : public LeaveHandler {
private:static const int MAX_DAYS_SUPERVISOR_CAN_APPROVE = 3;LeaveHandler* nextHandler;public:Supervisor(LeaveHandler* nextHandler) : nextHandler(nextHandler) {}void handleRequest(const std::string& name, int days) override {if (days <= MAX_DAYS_SUPERVISOR_CAN_APPROVE) {std::cout << name << " Approved by Supervisor." << std::endl;} else if (nextHandler != nullptr) {nextHandler->handleRequest(name, days);} else {std::cout << name << " Denied by Supervisor." << std::endl;}}
};class Manager : public LeaveHandler {
private:static const int MAX_DAYS_MANAGER_CAN_APPROVE = 7;LeaveHandler* nextHandler;public:Manager(LeaveHandler* nextHandler) : nextHandler(nextHandler) {}void handleRequest(const std::string& name, int days) override {if (days <= MAX_DAYS_MANAGER_CAN_APPROVE) {std::cout << name << " Approved by Manager." << std::endl;} else if (nextHandler != nullptr) {nextHandler->handleRequest(name, days);} else {std::cout << name << " Denied by Manager." << std::endl;}}
};class Director : public LeaveHandler {
private:static const int MAX_DAYS_DIRECTOR_CAN_APPROVE = 10;public:void handleRequest(const std::string& name, int days) override {if (days <= MAX_DAYS_DIRECTOR_CAN_APPROVE) {std::cout << name << " Approved by Director." << std::endl;} else {std::cout << name << " Denied by Director." << std::endl;}}
};class LeaveRequest {
private:std::string name;int days;public:LeaveRequest(const std::string& name, int days) : name(name), days(days) {}std::string getName() const {return name;}int getDays() const {return days;}
};int main() {int n;std::cin >> n;std::cin.ignore(); LeaveHandler* director = new Director();LeaveHandler* manager = new Manager(director);LeaveHandler* supervisor = new Supervisor(manager);for (int i = 0; i < n; i++) {std::string input;std::getline(std::cin, input);std::istringstream iss(input);std::string name;int days;if (iss >> name >> days) {LeaveRequest request(name, days);supervisor->handleRequest(name, days);} else {std::cout << "Invalid input" << std::endl;return 1;}}delete supervisor;delete manager;delete director;return 0;
}
13.2 擴展知識
責任鏈模式(Chain of Responsibility Pattern)是一種行為型設計模式,它通過建立一條對象鏈,使請求能夠沿著這條鏈依次傳遞,直到有一個對象處理它為止。這種模式避免了請求的發送者與接收者之間的緊耦合,讓多個對象都有機會處理請求。
簡單來說,責任鏈模式就像公司的審批流程:員工報銷需要先由部門經理審批,部門經理無權審批的大額報銷會提交給總監,總監也處理不了的會繼續提交給總經理,直到有人處理或流程結束。
核心角色:
- Handler(處理者接口)
定義處理請求的接口,通常包含一個指向后繼處理者的引用。 - ConcreteHandler(具體處理者)
實現處理者接口,負責處理特定范圍內的請求;如果無法處理,則將請求轉發給后繼者。 - Client(客戶端)
創建處理者鏈,并發起請求(無需關心具體由誰處理)。
適用場景:
- 當多個對象都可能處理同一請求,且具體處理者不確定(需運行時確定)時。
- 當需要動態指定處理請求的對象集合時。
- 當希望請求的發送者與接收者解耦時(發送者無需知道誰會處理請求)。
C++ 代碼示例:
下面以 “請假審批流程” 為例:員工請假需要根據請假天數,由不同層級的領導審批(組長可批 3 天內,經理可批 7 天內,總監可批 30 天內,超過 30 天拒絕)。
#include <iostream>
#include <string>// 1. 處理者接口:審批者
class Approver {
protected:Approver* nextApprover; // 后繼審批者std::string name; // 審批者姓名public:Approver(const std::string& n, Approver* next = nullptr) : name(n), nextApprover(next) {}virtual ~Approver() {// 遞歸銷毀責任鏈delete nextApprover;nextApprover = nullptr;}// 處理請求的接口(純虛函數)virtual void approve(int days, const std::string& employee) = 0;
};// 2. 具體處理者1:組長(審批3天以內)
class TeamLeader : public Approver {
public:TeamLeader(const std::string& n, Approver* next = nullptr) : Approver(n, next) {}void approve(int days, const std::string& employee) override {if (days <= 3) {std::cout << "組長" << name << "批準了" << employee << "的" << days << "天假期。\n";} else if (nextApprover) {// 無法處理,轉發給下一個審批者std::cout << "組長" << name << "無法批準" << days << "天假期,提交給上級...\n";nextApprover->approve(days, employee);} else {std::cout << "無人處理" << employee << "的" << days << "天假期申請。\n";}}
};// 2. 具體處理者2:經理(審批7天以內)
class Manager : public Approver {
public:Manager(const std::string& n, Approver* next = nullptr) : Approver(n, next) {}void approve(int days, const std::string& employee) override {if (days <= 7) {std::cout << "經理" << name << "批準了" << employee << "的" << days << "天假期。\n";} else if (nextApprover) {std::cout << "經理" << name << "無法批準" << days << "天假期,提交給上級...\n";nextApprover->approve(days, employee);} else {std::cout << "無人處理" << employee << "的" << days << "天假期申請。\n";}}
};// 2. 具體處理者3:總監(審批30天以內)
class Director : public Approver {
public:Director(const std::string& n, Approver* next = nullptr) : Approver(n, next) {}void approve(int days, const std::string& employee) override {if (days <= 30) {std::cout << "總監" << name << "批準了" << employee << "的" << days << "天假期。\n";} else if (nextApprover) {std::cout << "總監" << name << "無法批準" << days << "天假期,提交給上級...\n";nextApprover->approve(days, employee);} else {std::cout << employee << "的" << days << "天假期申請被拒絕(超過最大允許天數)。\n";}}
};// 客戶端代碼
int main() {// 構建責任鏈:組長 → 經理 → 總監Approver* chain = new TeamLeader("張三", new Manager("李四", new Director("王五")));// 發起不同的請假請求std::cout << "=== 請假2天 ===" << std::endl;chain->approve(2, "小明");std::cout << "\n=== 請假5天 ===" << std::endl;chain->approve(5, "小紅");std::cout << "\n=== 請假20天 ===" << std::endl;chain->approve(20, "小剛");std::cout << "\n=== 請假40天 ===" << std::endl;chain->approve(40, "小強");// 清理責任鏈delete chain;return 0;
}
十四、裝飾模式
14.1 回答重點
大話設計模式:(Decorator),動態地給一個對象添加一些額外地指責,就增加功能來說,裝飾模式比生成子類更靈活。
通常情況下,擴展類的功能可以通過繼承實現,但是擴展越多,子類越多,裝飾模式(Decorator Pattern
, 結構型設計模式)可以在不定義子類的情況下動態的給對象添加一些額外的功能。具體的做法是將原始對象放入包含行為的特殊封裝類(裝飾類),從而為原始對象動態添加新的行為,而無需修改其代碼。
舉個簡單的例子,假設你有一個基礎的圖形類,你想要為圖形類添加顏色、邊框、陰影等功能,如果每個功能都實現一個子類,就會導致產生大量的類,這時就可以考慮使用裝飾模式來動態地添加,而不需要修改圖形類本身的代碼,這樣可以使得代碼更加靈活、更容易維護和擴展。
基本結構:
-
組件
Component
:通常是抽象類或者接口,是具體組件和裝飾者的父類,定義了具體組件需要實現的方法,比如說我們定義Coffee
為組件。 -
具體組件
ConcreteComponent
: 實現了Component接口的具體類,是被裝飾的對象。 -
裝飾類
Decorator
: 一個抽象類,給具體組件添加功能,但是具體的功能由其子類具體裝飾者完成,持有一個指向Component對象的引用。 -
具體裝飾類
ConcreteDecorator
: 擴展Decorator類,負責向Component對象添加新的行為,加牛奶的咖啡是一個具體裝飾類,加糖的咖啡也是一個具體裝飾類。
應用場景:
裝飾模式通常在以下幾種情況使用:
-
當需要給一個現有類添加附加功能,但由于某些原因不能使用繼承來生成子類進行擴充時,可以使用裝飾模式。
-
動態的添加和覆蓋功能:當對象的功能要求可以動態地添加,也可以再動態地撤銷時可以使用裝飾模式。
在Java的I/O庫中,裝飾者模式被廣泛用于增強I/O流的功能。例如,BufferedInputStream
和BufferedOutputStream
這兩個類提供了緩沖區的支持,通過在底層的輸入流和輸出流上添加緩沖區,提高了讀寫的效率,它們都是InputStream
和OutputStream
的裝飾器。BufferedReader
和BufferedWriter
這兩個類與BufferedInputStream
和BufferedOutputStream
類似,提供了字符流的緩沖功能,是Reader和Writer的裝飾者。
示例代碼:
// 咖啡接口
class Coffee {
public:virtual ~Coffee() {}virtual void brew() = 0;
};// 具體的黑咖啡類
class BlackCoffee : public Coffee {
public:void brew() override {std::cout << "Brewing Black Coffee" << std::endl;}
};// 具體的拿鐵類
class Latte : public Coffee {
public:void brew() override {std::cout << "Brewing Latte" << std::endl;}
};// 裝飾者抽象類
class Decorator : public Coffee {
protected:std::unique_ptr<Coffee> coffee;public:Decorator(std::unique_ptr<Coffee> coffee) : coffee(std::move(coffee)) {}void brew() override {if (coffee) {coffee->brew();}}
};// 具體的牛奶裝飾者類
class MilkDecorator : public Decorator {
public:MilkDecorator(std::unique_ptr<Coffee> coffee) :Decorator(std::move(coffee)){}void brew() override {Decorator::brew();std::cout << "Adding Milk" << std::endl;}
};// 具體的糖裝飾者類
class SugarDecorator : public Decorator {
public:SugarDecorator(std::unique_ptr<Coffee> coffee) : Decorator(std::move(coffee)) {}void brew() override {Decorator::brew();std::cout << "Adding Sugar" << std::endl;}
};// 客戶端代碼
int main() {int coffeeType, condimentType;while (std::cin >> coffeeType >> condimentType) {// 根據輸入制作咖啡std::unique_ptr<Coffee> coffee;if (coffeeType == 1) {coffee = std::make_unique<BlackCoffee>();} else if (coffeeType == 2) {coffee = std::make_unique<Latte>();} else {std::cout << "Invalid coffee type" << std::endl;continue;}// 根據輸入添加調料if (condimentType == 1) {coffee = std::make_unique<MilkDecorator>(std::move(coffee));} else if (condimentType == 2) {coffee = std::make_unique<SugarDecorator>(std::move(coffee));} else {std::cout << "Invalid condiment type" << std::endl;continue;}// 輸出制作過程coffee->brew();}return 0;
}
14.2 擴展知識
裝飾器模式(Decorator Pattern)是一種結構型設計模式,它允許在不改變原有對象結構的前提下,動態地給對象添加額外功能。這種模式通過創建 “裝飾器” 包裝原始對象,以層層嵌套的方式擴展功能,比繼承更加靈活。
簡單來說,裝飾器模式就像給手機套殼:手機(原始對象)的核心功能不變,但可以套上保護殼(基礎裝飾)、帶支架的殼(增強裝飾)、防水殼(特殊裝飾)等,每個裝飾都在不改變手機本身的情況下增加了新功能。
核心角色:
- Component(抽象組件)
定義原始對象和裝飾器的共同接口,確保裝飾器可以替代原始對象。 - ConcreteComponent(具體組件)
實現抽象組件接口,是被裝飾的原始對象(如手機本身)。 - Decorator(抽象裝飾器)
實現抽象組件接口,并持有一個組件對象的引用,定義所有具體裝飾器的基類。 - ConcreteDecorator(具體裝飾器)
繼承抽象裝飾器,在調用原始組件方法前后添加額外功能(如手機殼的保護功能)。
適用場景:
- 當需要給一個對象動態添加功能,且這些功能可以靈活組合時。
- 當不適合用繼承(會導致類爆炸)來擴展功能時。
- 當需要在運行時動態增加或移除對象的功能時。
C++ 代碼示例:
下面以 “咖啡訂單” 為例:基礎咖啡可以添加牛奶、糖、巧克力等配料(裝飾器),每種配料會增加價格并改變描述,且配料可以任意組合
#include <iostream>
#include <string>// 1. 抽象組件:咖啡
class Coffee {
public:virtual ~Coffee() = default;virtual std::string getDescription() const = 0; // 獲取描述virtual double getPrice() const = 0; // 獲取價格
};// 2. 具體組件:基礎咖啡(被裝飾的原始對象)
class Espresso : public Coffee {
public:std::string getDescription() const override {return "濃縮咖啡";}double getPrice() const override {return 20.0; // 基礎價格}
};// 3. 抽象裝飾器:配料裝飾器
class CoffeeDecorator : public Coffee {
protected:Coffee* coffee; // 被裝飾的咖啡對象public:CoffeeDecorator(Coffee* c) : coffee(c) {}~CoffeeDecorator() override {delete coffee; // 負責釋放被裝飾對象}
};// 4. 具體裝飾器1:牛奶
class Milk : public CoffeeDecorator {
public:Milk(Coffee* c) : CoffeeDecorator(c) {}// 擴展描述:添加"加牛奶"std::string getDescription() const override {return coffee->getDescription() + ",加牛奶";}// 擴展價格:增加5元double getPrice() const override {return coffee->getPrice() + 5.0;}
};// 4. 具體裝飾器2:糖
class Sugar : public CoffeeDecorator {
public:Sugar(Coffee* c) : CoffeeDecorator(c) {}std::string getDescription() const override {return coffee->getDescription() + ",加糖";}double getPrice() const override {return coffee->getPrice() + 2.0;}
};// 4. 具體裝飾器3:巧克力
class Chocolate : public CoffeeDecorator {
public:Chocolate(Coffee* c) : CoffeeDecorator(c) {}std::string getDescription() const override {return coffee->getDescription() + ",加巧克力";}double getPrice() const override {return coffee->getPrice() + 8.0;}
};// 客戶端代碼
int main() {// 點一杯純濃縮咖啡Coffee* coffee1 = new Espresso();std::cout << "訂單1: " << coffee1->getDescription() << ",價格: " << coffee1->getPrice() << "元\n";// 點一杯加牛奶和糖的咖啡(多層裝飾)Coffee* coffee2 = new Milk(new Sugar(new Espresso()));std::cout << "訂單2: " << coffee2->getDescription() << ",價格: " << coffee2->getPrice() << "元\n";// 點一杯加巧克力、牛奶和糖的咖啡(更多層裝飾)Coffee* coffee3 = new Chocolate(new Milk(new Sugar(new Espresso())));std::cout << "訂單3: " << coffee3->getDescription() << ",價格: " << coffee3->getPrice() << "元\n";// 清理資源(裝飾器會遞歸釋放被裝飾對象)delete coffee1;delete coffee2;delete coffee3;return 0;
}
代碼說明:
- Coffee(抽象組件):定義了咖啡的基本接口(
getDescription()
和getPrice()
),確保所有咖啡(包括原始咖啡和裝飾后的咖啡)都遵循同一接口。 - Espresso(具體組件):代表基礎咖啡,是所有裝飾的起點,實現了基礎描述和價格。
- CoffeeDecorator(抽象裝飾器):
- 繼承
Coffee
接口,確保裝飾器可以像原始咖啡一樣被使用(里氏替換原則)。 - 持有一個
Coffee
指針,指向被裝飾的對象(可以是原始咖啡或其他裝飾器)。 - 析構函數負責釋放被裝飾對象,避免內存泄漏。
- 繼承
- 具體裝飾器:
Milk
、Sugar
、Chocolate
分別實現了添加對應配料的功能。- 每個裝飾器在調用被裝飾對象的方法(如
getDescription()
)基礎上,添加了自己的擴展(如 “加牛奶” 和額外價格)。
運行結果
訂單1: 濃縮咖啡,價格: 20元
訂單2: 濃縮咖啡,加糖,加牛奶,價格: 27元
訂單3: 濃縮咖啡,加糖,加牛奶,加巧克力,價格: 35元
裝飾器模式的優點
- 靈活性高:可以動態組合不同的裝飾器,實現多種功能組合(如牛奶 + 糖、巧克力 + 牛奶等),比繼承更靈活。
- 遵循開閉原則:新增功能只需添加新的裝飾器,無需修改原始對象或現有裝飾器。
- 功能隔離:每個裝飾器只關注單一功能(如牛奶只負責添加牛奶相關的描述和價格),職責單一。
與繼承的對比
- 繼承是靜態的(編譯時確定功能),裝飾器是動態的(運行時組合功能)。
- 繼承會導致類數量爆炸(如
加牛奶的咖啡
、加糖的咖啡
、加牛奶和糖的咖啡
需要多個類),而裝飾器通過組合實現,類數量少。
裝飾器模式在實際開發中應用廣泛,例如:
- Java 的
IO流
(如BufferedInputStream
裝飾FileInputStream
)。 - GUI 組件的擴展(如給按鈕添加邊框、陰影等裝飾)。
- 日志記錄、性能監控等橫切關注點的實現。
通過裝飾器模式,可以在不改變原始對象的情況下,靈活地擴展功能,是代碼設計中實現 “組合優于繼承” 的重要手段。
十五、命令模式
15.1 回答重點
**大話設計模式:**命令模式(Command),將一個請求封裝為一個對象,從而使得你用不同地請求,對客戶進行參數化,對請求排隊或記錄請求日志,以及支持可撤銷地操作。
命令模式是一種行為型設計模式,其允許將請求封裝成一個對象(命令對象,包含執行操作所需的所有信息),并將命令對象按照一定的順序存儲在隊列中,然后再逐一調用執行,這些命令也可以支持反向操作,進行撤銷和重做。
這樣一來,發送者只需要觸發命令就可以完成操作,不需要知道接受者的具體操作,從而實現兩者間的解耦。
基本結構:
-
命令接口
Command
:接口或者抽象類,定義執行操作的接口。 -
具體命令類
ConcreteCommand
: 實現命令接口,執行具體操作,在調用execute
方法時使“接收者對象”根據命令完成具體的任務,比如遙控器中的“開機”,“關機”命令。 -
接收者類
Receiver
: 接受并執行命令的對象,可以是任何對象,遙控器可以控制空調,也可以控制電視機,電視機和空調負責執行具體操作,是接收者。 -
調用者類
Invoker
: 發起請求的對象,有一個將命令作為參數傳遞的方法。它不關心命令的具體實現,只負責調用命令對象的execute()
方法來傳遞請求,在本例中,控制遙控器的“人”就是調用者。 -
客戶端:創建具體的命令對象和接收者對象,然后將它們組裝起來。
應用場景:
命令模式同樣有著很多現實場景的應用,比如Git中的很多操作,如提交(commit)、合并(merge)等,都可以看作是命令模式的應用,用戶通過執行相應的命令來操作版本庫。Java的GUI編程中,很多事件處理機制也都使用了命令模式。例如,每個按鈕都有一個關聯的 Action
,它代表一個命令,按鈕的點擊觸發 Action
的執行。
示例代碼:
class DrinkMaker; // 前向聲明// 命令接口
class Command {
public:virtual void execute() = 0;virtual ~Command() = default; // 添加虛析構函數
};// 具體命令類 - 點餐命令
class OrderCommand : public Command {
private:std::string drinkName;DrinkMaker* receiver; // 使用前向聲明public:OrderCommand(const std::string& drinkName, DrinkMaker* receiver);void execute() override;
};// 接收者類 - 制作飲品
class DrinkMaker {
public:void makeDrink(const std::string& drinkName) {std::cout << drinkName << " is ready!" << std::endl;}
};// 實現 OrderCommand 的構造函數和 execute 函數
OrderCommand::OrderCommand(const std::string& drinkName, DrinkMaker* receiver) : drinkName(drinkName), receiver(receiver) {}void OrderCommand::execute() {receiver->makeDrink(drinkName);
}// 調用者類 - 點餐機
class OrderMachine {
private:Command* command;public:void setCommand(Command* command) {this->command = command;}void executeOrder() {command->execute();}
};int main() {// 創建接收者和命令對象DrinkMaker drinkMaker;// 讀取命令數量int n;std::cin >> n;std::cin.ignore(); // 消耗掉換行符while (n-- > 0) {// 讀取命令std::string drinkName;std::cin >> drinkName;// 創建命令對象Command* command = new OrderCommand(drinkName, &drinkMaker);// 執行命令OrderMachine orderMachine;orderMachine.setCommand(command);orderMachine.executeOrder();// 釋放動態分配的命令對象delete command;}return 0;
}
15.2 擴展知識
命令模式(Command Pattern)是一種行為型設計模式,它將請求封裝為一個對象,使你可以用不同的請求參數化其他對象,并且支持請求的排隊、記錄日志和撤銷操作。
簡單來說,命令模式就像餐廳點餐:顧客(客戶端)向服務員(調用者)下達命令(點單),服務員將命令傳遞給廚師(接收者)執行。這里的 “訂單” 就是封裝后的命令對象,包含了菜品信息和執行方式。
核心角色:
- Command(命令接口)
聲明執行操作的接口(通常是一個execute()
方法)。 - ConcreteCommand(具體命令)
實現命令接口,綁定接收者和具體操作,調用接收者的方法完成命令。 - Receiver(接收者)
知道如何執行命令的具體操作,是實際業務邏輯的執行者。 - Invoker(調用者)
持有命令對象,負責在合適的時機調用命令的execute()
方法。 - Client(客戶端)
創建具體命令對象,并設置其接收者。
適用場景:
- 需要將請求的發送者和接收者解耦時(發送者無需知道接收者是誰以及如何處理)。
- 需要支持命令的撤銷、重做、排隊執行或記錄日志時。
- 需要支持事務操作(一組命令要么全部執行,要么全部不執行)時。
C++ 代碼示例:
下面以 “智能家居控制” 為例:遙控器(調用者)可以發送各種命令(開燈、關燈、開空調等),每個命令對應不同的設備(接收者),且支持命令撤銷功能。
#include <iostream>
#include <vector>
#include <string>// 前向聲明接收者
class Light;
class AirConditioner;// 1. 命令接口
class Command {
public:virtual ~Command() = default;virtual void execute() = 0; // 執行命令virtual void undo() = 0; // 撤銷命令virtual std::string getName() = 0; // 獲取命令名稱
};// 3. 接收者1:電燈
class Light {
private:std::string location;bool isOn;public:Light(const std::string& loc) : location(loc), isOn(false) {}void on() {isOn = true;std::cout << location << "電燈已打開\n";}void off() {isOn = false;std::cout << location << "電燈已關閉\n";}bool getState() const { return isOn; }
};// 3. 接收者2:空調
class AirConditioner {
private:std::string location;bool isOn;int temperature;public:AirConditioner(const std::string& loc) : location(loc), isOn(false), temperature(26) {}void on() {isOn = true;std::cout << location << "空調已打開,當前溫度: " << temperature << "°C\n";}void off() {isOn = false;std::cout << location << "空調已關閉\n";}void setTemperature(int temp) {temperature = temp;std::cout << location << "空調溫度設置為: " << temperature << "°C\n";}bool getState() const { return isOn; }int getTemperature() const { return temperature; }
};// 2. 具體命令1:開燈命令
class LightOnCommand : public Command {
private:Light* light;bool prevState; // 用于撤銷操作public:LightOnCommand(Light* l) : light(l) {}void execute() override {prevState = light->getState();light->on();}void undo() override {if (prevState) {light->on();} else {light->off();}std::cout << "撤銷" << getName() << "\n";}std::string getName() override {return "開燈命令";}
};// 2. 具體命令2:關燈命令
class LightOffCommand : public Command {
private:Light* light;bool prevState;public:LightOffCommand(Light* l) : light(l) {}void execute() override {prevState = light->getState();light->off();}void undo() override {if (prevState) {light->on();} else {light->off();}std::cout << "撤銷" << getName() << "\n";}std::string getName() override {return "關燈命令";}
};// 2. 具體命令3:開空調命令
class AirConditionerOnCommand : public Command {
private:AirConditioner* ac;bool prevState;public:AirConditionerOnCommand(AirConditioner* a) : ac(a) {}void execute() override {prevState = ac->getState();ac->on();}void undo() override {if (prevState) {ac->on();} else {ac->off();}std::cout << "撤銷" << getName() << "\n";}std::string getName() override {return "開空調命令";}
};// 4. 調用者:遙控器
class RemoteControl {
private:std::vector<Command*> commands; // 命令列表Command* lastCommand; // 記錄最后執行的命令(用于撤銷)public:RemoteControl(int buttonCount) {commands.resize(buttonCount, nullptr);lastCommand = nullptr;}~RemoteControl() {for (auto cmd : commands) {delete cmd;}delete lastCommand;}// 設置按鈕對應的命令void setCommand(int slot, Command* cmd) {if (slot >= 0 && slot < commands.size()) {delete commands[slot]; // 釋放舊命令commands[slot] = cmd;}}// 按下按鈕執行命令void pressButton(int slot) {if (slot >= 0 && slot < commands.size() && commands[slot]) {commands[slot]->execute();// 保存最后執行的命令(用于撤銷)delete lastCommand;lastCommand = commands[slot]; // 注意:實際應用中可能需要命令克隆} else {std::cout << "無效的按鈕或未設置命令\n";}}// 撤銷最后一個命令void pressUndo() {if (lastCommand) {lastCommand->undo();} else {std::cout << "沒有可撤銷的命令\n";}}
};// 客戶端代碼
int main() {// 創建接收者(設備)Light* livingRoomLight = new Light("客廳");AirConditioner* bedroomAC = new AirConditioner("臥室");// 創建具體命令Command* lightOn = new LightOnCommand(livingRoomLight);Command* lightOff = new LightOffCommand(livingRoomLight);Command* acOn = new AirConditionerOnCommand(bedroomAC);// 創建調用者(遙控器,有3個按鈕)RemoteControl* remote = new RemoteControl(3);// 給遙控器按鈕分配命令remote->setCommand(0, lightOn); // 按鈕0:開燈remote->setCommand(1, lightOff); // 按鈕1:關燈remote->setCommand(2, acOn); // 按鈕2:開空調// 模擬操作遙控器std::cout << "=== 按下按鈕0 ===" << std::endl;remote->pressButton(0);std::cout << "\n=== 按下按鈕2 ===" << std::endl;remote->pressButton(2);std::cout << "\n=== 按下按鈕1 ===" << std::endl;remote->pressButton(1);std::cout << "\n=== 按下撤銷按鈕 ===" << std::endl;remote->pressUndo();std::cout << "\n=== 再次按下撤銷按鈕 ===" << std::endl;remote->pressUndo();// 清理資源delete remote;delete livingRoomLight;delete bedroomAC;return 0;
}d
代碼說明
- Command(命令接口):定義了
execute()
(執行)和undo()
(撤銷)方法,所有具體命令都必須實現這兩個方法。 - 具體命令類:
- 如
LightOnCommand
、LightOffCommand
等,每個命令綁定一個接收者(如Light
、AirConditioner
)。 - 在
execute()
中調用接收者的對應方法(如light->on()
),并記錄執行前的狀態用于撤銷。 undo()
方法根據記錄的狀態恢復到命令執行前的狀態。
- 如
- 接收者:
Light
和AirConditioner
類,包含實際的業務邏輯(開燈、關燈、開空調等),不知道命令的存在。 - 調用者:
RemoteControl
(遙控器)類,持有命令列表,負責調用命令的execute()
和undo()
方法,不關心命令的具體實現。
運行結果:
=== 按下按鈕0 ===
客廳電燈已打開=== 按下按鈕2 ===
臥室空調已打開,當前溫度: 26°C=== 按下按鈕1 ===
客廳電燈已關閉=== 按下撤銷按鈕 ===
客廳電燈已打開
撤銷關燈命令=== 再次按下撤銷按鈕 ===
臥室空調已關閉
撤銷開空調命令
命令模式的優點:
- 解耦發送者和接收者:發送者(遙控器)無需知道接收者(電燈、空調)的具體實現,只需調用命令的
execute()
方法。 - 支持撤銷和日志:通過記錄命令歷史和實現
undo()
方法,可以輕松實現撤銷、重做功能,也便于記錄操作日志。 - 支持批量操作:可以將多個命令組合成復合命令(如 “回家模式” 包含開燈、開空調等),實現事務性操作。
- 擴展性好:新增命令只需添加新的具體命令類,無需修改現有代碼(符合開閉原則)。
命令模式在很多場景中都有應用,例如:GUI 中的菜單命令、撤銷 / 重做功能、任務調度系統、事務管理等,核心是通過封裝請求為對象,實現請求的靈活管理和擴展。