迭代器模式(Iterator Pattern)詳解
一、核心概念
迭代器模式提供一種方法來順序訪問一個聚合對象中的各個元素,而又不暴露該對象的內部表示。該模式將遍歷邏輯封裝在迭代器對象中,使聚合對象和遍歷邏輯分離。
核心組件:
- 迭代器接口(Iterator):定義訪問和遍歷元素的方法。
- 具體迭代器(Concrete Iterator):實現迭代器接口,跟蹤聚合對象中的當前位置。
- 聚合接口(Aggregate):定義創建迭代器對象的方法。
- 具體聚合(Concrete Aggregate):實現聚合接口,返回一個具體迭代器實例。
二、代碼示例:自定義集合迭代器
場景:實現一個簡單的數組集合,并為其提供迭代器支持。
#include <iostream>
#include <memory>// 前向聲明
template<typename T> class ArrayIterator;// 迭代器接口
template<typename T>
class Iterator {
public:virtual ~Iterator() = default;virtual bool hasNext() const = 0;virtual T& next() = 0;virtual const T& next() const = 0;
};// 聚合接口
template<typename T>
class Aggregate {
public:virtual ~Aggregate() = default;virtual std::unique_ptr<Iterator<T>> createIterator() const = 0;virtual size_t size() const = 0;virtual T& operator[](size_t index) = 0;virtual const T& operator[](size_t index) const = 0;
};// 具體聚合:數組集合
template<typename T, size_t N>
class Array : public Aggregate<T> {
private:T data[N];size_t count = 0;public:void add(const T& item) {if (count < N) {data[count++] = item;}}size_t size() const override { return count; }T& operator[](size_t index) override { return data[index]; }const T& operator[](size_t index) const override { return data[index]; }std::unique_ptr<Iterator<T>> createIterator() const override;
};// 具體迭代器:數組迭代器
template<typename T, size_t N>
class ArrayIterator : public Iterator<T> {
private:const Array<T, N>* array;size_t position;public:explicit ArrayIterator(const Array<T, N>* array) : array(array), position(0) {}bool hasNext() const override {return position < array->size();}T& next() override {return (*array)[position++];}const T& next() const override {return (*array)[position++];}
};// 實現createIterator方法
template<typename T, size_t N>
std::unique_ptr<Iterator<T>> Array<T, N>::createIterator() const {return std::make_unique<ArrayIterator<T, N>>(this);
}// 客戶端代碼
int main() {Array<int, 5> numbers;numbers.add(10);numbers.add(20);numbers.add(30);// 使用迭代器遍歷集合auto it = numbers.createIterator();while (it->hasNext()) {std::cout << it->next() << " ";}std::cout << std::endl;// C++范圍for循環風格(需額外實現begin/end)// 此處省略具體實現...return 0;
}
三、迭代器模式的優勢
-
分離遍歷邏輯:
- 聚合對象的結構與遍歷邏輯分離,符合單一職責原則。
-
簡化聚合接口:
- 聚合類無需提供復雜的遍歷接口,僅需創建迭代器。
-
支持多種遍歷方式:
- 同一聚合對象可支持多種迭代器(如正向、反向、過濾迭代器)。
-
統一訪問接口:
- 客戶端通過統一的迭代器接口訪問不同聚合結構,提高代碼通用性。
四、實現變種
-
內部迭代器 vs 外部迭代器:
- 外部迭代器(如示例):客戶端控制迭代過程。
- 內部迭代器:迭代邏輯由迭代器自身控制,客戶端提供回調函數。
-
雙向迭代器:
- 支持向前和向后遍歷(如
prev()
方法)。
- 支持向前和向后遍歷(如
-
流式迭代器:
- 支持惰性計算,適用于大數據集(如數據庫查詢結果)。
-
組合迭代器:
- 遍歷組合模式中的樹形結構(如文件系統)。
五、適用場景
-
隱藏聚合實現:
- 當需要隱藏聚合對象的內部結構(如數組、鏈表、樹)時。
-
支持多種遍歷方式:
- 如順序遍歷、隨機訪問、過濾遍歷等。
-
統一遍歷接口:
- 為不同類型的聚合對象提供一致的遍歷接口。
-
簡化客戶端代碼:
- 使客戶端無需關心聚合對象的具體類型,專注于元素處理。
六、注意事項
-
迭代器失效:
- 在迭代過程中修改聚合對象可能導致迭代器失效(如刪除元素)。
-
線程安全:
- 在多線程環境中,需確保迭代器的線程安全性。
-
與語言特性結合:
- C++ 標準庫已提供
std::iterator
和容器迭代器,優先使用現有實現。
- C++ 標準庫已提供
-
性能開銷:
- 間接訪問可能帶來性能開銷,需根據場景優化。
七、與其他模式的對比
-
與訪問者模式的區別:
- 迭代器模式專注于遍歷,訪問者模式專注于元素處理。
-
與組合模式的結合:
- 組合模式的樹形結構常使用迭代器模式遍歷。
-
與生成器模式的關系:
- 生成器模式可用于動態生成迭代器所需的元素。
迭代器模式是處理聚合對象遍歷的經典解決方案,通過將遍歷邏輯封裝在迭代器中,使代碼更簡潔、靈活且易于維護。在實際開發中,建議優先使用編程語言提供的內置迭代器(如 C++ 的 STL 迭代器),避免重復造輪子。
單例模式(Singleton Pattern)詳解
一、核心概念
單例模式確保一個類只有一個實例,并提供一個全局訪問點來獲取該實例。該模式常用于需要全局唯一對象的場景,如配置管理器、日志記錄器、數據庫連接池等。
核心組件:
- 私有構造函數:防止外部直接實例化。
- 靜態實例:持有類的唯一實例。
- 靜態訪問方法:提供全局訪問點。
二、代碼示例
1. 懶漢式(Lazy Initialization)
// 線程不安全版本(單線程環境)
class Singleton {
private:static Singleton* instance;Singleton() = default; // 私有構造函數~Singleton() = default; // 私有析構函數Singleton(const Singleton&) = delete; // 禁用拷貝構造Singleton& operator=(const Singleton&) = delete; // 禁用賦值運算符public:static Singleton* getInstance() {if (instance == nullptr) { // 第一次調用時初始化instance = new Singleton();}return instance;}
};// 靜態成員初始化
Singleton* Singleton::instance = nullptr;
2. 線程安全的懶漢式(雙檢鎖)
#include <mutex>class Singleton {
private:static Singleton* instance;static std::mutex mutex_;Singleton() = default;public:static Singleton* getInstance() {if (instance == nullptr) { // 第一次檢查std::lock_guard<std::mutex> lock(mutex_);if (instance == nullptr) { // 第二次檢查(鎖內)instance = new Singleton();}}return instance;}
};Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex_;
3. 餓漢式(Eager Initialization)
class Singleton {
private:static const Singleton* instance;Singleton() = default;public:static const Singleton* getInstance() {return instance;}
};// 靜態初始化(在main函數前完成)
const Singleton* Singleton::instance = new Singleton();
4. C++11 標準的 Meyers’ Singleton(推薦)
class Singleton {
private:Singleton() = default;~Singleton() = default;Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;public:static Singleton& getInstance() {static Singleton instance; // 線程安全的局部靜態變量return instance;}
};
三、單例模式的優勢
-
全局唯一性:
- 確保系統中一個類只有一個實例,便于資源控制。
-
全局訪問點:
- 提供統一的訪問方式,無需傳遞對象引用。
-
延遲初始化:
- 懶漢式實現支持延遲加載,節省資源。
-
嚴格控制訪問:
- 通過私有構造函數和靜態方法,嚴格控制實例化過程。
四、實現變種
-
多線程安全:
- 使用雙檢鎖(DCLP)或 C++11 的局部靜態變量保證線程安全。
-
可銷毀單例:
- 添加靜態銷毀方法,確保正確釋放資源。
-
模板單例:
- 使用模板實現通用單例基類,支持多個類復用單例邏輯。
-
參數化單例:
- 在首次調用時傳入參數初始化單例。
五、適用場景
-
資源管理器:
- 如數據庫連接池、文件系統管理器。
-
配置管理:
- 全局配置信息的讀寫。
-
日志系統:
- 集中記錄系統運行狀態。
-
GUI 組件:
- 如窗口管理器、對話框工廠。
六、注意事項
-
線程安全:
- 在多線程環境下必須保證實例創建的原子性(如 Meyers’ Singleton)。
-
內存泄漏:
- 動態分配的實例需確保正確釋放(可使用智能指針或靜態局部變量)。
-
反序列化問題:
- 單例類需防止通過反序列化創建新實例。
-
繼承與多態:
- 單例類的子類可能導致多個實例,需謹慎設計。
七、與其他模式的對比
-
與靜態類的區別:
- 單例是對象,支持繼承和多態;靜態類是全局方法集合。
-
與工廠模式的結合:
- 單例可作為工廠模式的工廠,創建其他對象。
-
與享元模式的區別:
- 單例強調唯一實例,享元強調對象復用。
單例模式是解決全局唯一對象需求的經典方案,但需謹慎使用,避免濫用導致代碼耦合度高。在現代 C++ 中,推薦使用 Meyers’ Singleton 實現,既簡潔又安全。
合成/聚合復用原則(CARP)詳解
一、核心概念
合成/聚合復用原則(Composition/Aggregation Reuse Principle, CARP)主張:
“要盡量使用合成/聚合,而不是類繼承來達到復用的目的”。
- 合成(Composition):強擁有關系,部分與整體生命周期一致(如
std::unique_ptr
)。 - 聚合(Aggregation):弱擁有關系,部分可獨立于整體存在(如
std::shared_ptr
或引用)。
該原則強調通過對象組合實現復用,而非通過繼承擴展類功能,以降低代碼耦合度。
二、合成/聚合 vs 繼承
維度 | 合成/聚合(推薦) | 繼承(需謹慎) |
---|---|---|
耦合度 | 低(對象間松耦合) | 高(子類依賴父類實現) |
靈活性 | 運行時動態組合,支持多態 | 編譯時靜態綁定,難以修改 |
復用粒度 | 細粒度(復用具體組件) | 粗粒度(復用整個父類) |
依賴可見性 | 僅依賴接口,不暴露內部實現 | 可能暴露父類非公共接口 |
設計模式 | 策略模式、裝飾器模式、組合模式 | 模板方法模式、工廠方法模式 |
三、代碼示例:交通工具與引擎
1. 反例:使用繼承復用(高耦合)
// 父類:引擎
class Engine {
public:void start() { std::cout << "引擎啟動" << std::endl; }void stop() { std::cout << "引擎停止" << std::endl; }
};// 子類:汽車(繼承引擎)
class Car : public Engine {
public:void drive() {start(); // 直接復用父類方法std::cout << "汽車行駛" << std::endl;stop();}
};// 子類:飛機(繼承引擎)
class Plane : public Engine {
public:void fly() {start(); // 直接復用父類方法std::cout << "飛機起飛" << std::endl;stop();}
};
問題:
- 繼承導致
Car
和Plane
與Engine
強綁定,無法在運行時更換引擎類型。 - 若
Engine
修改接口,所有子類需同步修改。
2. 正例:使用聚合復用(低耦合)
// 接口:引擎
class IEngine {
public:virtual void start() = 0;virtual void stop() = 0;virtual ~IEngine() = default;
};// 具體引擎:燃油引擎
class GasolineEngine : public IEngine {
public:void start() override { std::cout << "燃油引擎啟動" << std::endl; }void stop() override { std::cout << "燃油引擎停止" << std::endl; }
};// 具體引擎:電動引擎
class ElectricEngine : public IEngine {
public:void start() override { std::cout << "電動引擎啟動" << std::endl; }void stop() override { std::cout << "電動引擎停止" << std::endl; }
};// 交通工具(聚合引擎)
class Vehicle {
private:std::unique_ptr<IEngine> engine; // 聚合:組合關系public:explicit Vehicle(std::unique_ptr<IEngine> engine) : engine(std::move(engine)) {}void setEngine(std::unique_ptr<IEngine> newEngine) {engine = std::move(newEngine);}void operate() {engine->start();std::cout << "交通工具運行" << std::endl;engine->stop();}
};// 客戶端代碼
void clientCode() {// 使用燃油引擎auto car = Vehicle(std::make_unique<GasolineEngine>());car.operate();// 動態更換為電動引擎car.setEngine(std::make_unique<ElectricEngine>());car.operate();
}
優勢:
- 通過聚合
IEngine
接口,Vehicle
與具體引擎實現解耦。 - 支持運行時動態切換引擎類型,符合開閉原則。
四、合成/聚合復用的典型場景
-
策略模式(Strategy Pattern)
通過組合不同策略對象實現算法切換:class SortStrategy { public:virtual void sort(std::vector<int>& data) = 0; };class QuickSort : public SortStrategy { /* 實現 */ }; class MergeSort : public SortStrategy { /* 實現 */ };class Sorter { private:std::unique_ptr<SortStrategy> strategy; public:void setStrategy(std::unique_ptr<SortStrategy> s) {strategy = std::move(s);}void performSort(std::vector<int>& data) {strategy->sort(data);} };
-
裝飾器模式(Decorator Pattern)
通過組合增強對象功能:class Component { public:virtual void operation() = 0; };class ConcreteComponent : public Component { /* 基礎實現 */ };class Decorator : public Component { protected:std::shared_ptr<Component> component; public:explicit Decorator(std::shared_ptr<Component> c) : component(c) {} };class LoggingDecorator : public Decorator { public:void operation() override {std::cout << "Before operation" << std::endl;component->operation();std::cout << "After operation" << std::endl;} };
五、使用指南
-
優先使用合成/聚合的場景:
- 需要動態改變對象行為時(如策略模式)。
- 復用細粒度組件而非整個類時。
- 避免繼承導致的“脆弱基類問題”(修改基類可能破壞子類)。
-
繼承的合理場景:
- 當子類是基類的“自然擴展”(如正方形是矩形的特例)。
- 復用基類的不變部分,擴展可變部分(如模板方法模式)。
- 實現多態接口(如抽象工廠模式)。
-
黃金法則:
- “HAS-A”關系優先用組合/聚合(如汽車有引擎)。
- “IS-A”關系謹慎用繼承(如正方形是矩形)。
六、注意事項
-
過度組合導致的問題:
- 可能增加對象數量和管理復雜度。
- 需合理設計接口,避免接口膨脹。
-
與依賴倒置原則(DIP)結合:
- 聚合對象應依賴抽象接口而非具體實現,進一步降低耦合。
-
繼承的替代方案:
- 委托(Delegation):通過對象組合實現行為委托。
- 混入(Mixin):通過模板或多重繼承復用特定功能。
合成/聚合復用原則是實現低耦合、高內聚設計的關鍵,它通過對象組合替代繼承,使系統更靈活、可維護和可擴展。在設計時,應根據具體場景權衡繼承與組合的使用,避免過度依賴單一復用方式。
在C++中,無法在運行時動態確定子類繼承自哪個基類。這是因為C++的繼承關系是靜態綁定的,必須在編譯時明確指定。不過,你可以通過以下幾種方式模擬運行時選擇基類的行為:
一、為什么不能在運行時確定繼承關系?
-
編譯時確定內存布局:
子類的內存布局(包括基類子對象的偏移量)在編譯時必須確定,否則無法進行對象訪問。 -
靜態類型系統限制:
C++的類型系統要求在編譯時明確類的繼承結構,以保證類型安全。
二、替代方案
1. 組合(Composition)替代繼承
思路:在子類中包含基類指針,運行時動態選擇基類實現。
#include <memory>// 基接口
class Base {
public:virtual void doSomething() = 0;virtual ~Base() = default;
};// 具體實現類A
class ConcreteA : public Base {
public:void doSomething() override { /* 實現A */ }
};// 具體實現類B
class ConcreteB : public Base {
public:void doSomething() override { /* 實現B */ }
};// 動態選擇行為的類
class DynamicClass {
private:std::unique_ptr<Base> strategy; // 組合基類指針public:// 在運行時設置行為void setStrategy(std::unique_ptr<Base> strategy) {this->strategy = std::move(strategy);}// 委托調用void performAction() {if (strategy) strategy->doSomething();}
};// 運行時選擇示例
void runtimeSelection(bool condition) {DynamicClass obj;if (condition) {obj.setStrategy(std::make_unique<ConcreteA>());} else {obj.setStrategy(std::make_unique<ConcreteB>());}obj.performAction(); // 根據condition選擇不同實現
}
2. 模板(編譯時多態)
思路:通過模板參數在編譯時選擇基類。
// 基類A
class BaseA {
public:void method() { /* 基類A的實現 */ }
};// 基類B
class BaseB {
public:void method() { /* 基類B的實現 */ }
};// 模板子類:編譯時選擇基類
template<typename BaseType>
class Derived : public BaseType {
public:void callBaseMethod() {this->method(); // 調用所選基類的方法}
};// 編譯時選擇示例
void compileTimeSelection(bool condition) {if (condition) {Derived<BaseA> obj; // 繼承自BaseAobj.callBaseMethod();} else {Derived<BaseB> obj; // 繼承自BaseBobj.callBaseMethod();}
}
3. 多重繼承 + 運行時類型切換
思路:繼承所有可能的基類,運行時通過接口選擇具體行為。
// 接口A
class InterfaceA {
public:virtual void actionA() = 0;
};// 接口B
class InterfaceB {
public:virtual void actionB() = 0;
};// 實現類同時繼承兩個接口
class MultiBase : public InterfaceA, public InterfaceB {
public:void actionA() override { /* 實現A */ }void actionB() override { /* 實現B */ }
};// 運行時選擇接口
void runtimeInterfaceSelection(bool useA) {MultiBase obj;if (useA) {InterfaceA* ptr = &obj;ptr->actionA(); // 使用接口A} else {InterfaceB* ptr = &obj;ptr->actionB(); // 使用接口B}
}
4. 動態加載插件(運行時動態綁定)
思路:通過插件系統在運行時加載不同的實現庫。
// 公共接口
class PluginInterface {
public:virtual void execute() = 0;virtual ~PluginInterface() = default;
};// 插件工廠(簡化版)
class PluginFactory {
public:static std::unique_ptr<PluginInterface> createPlugin(const std::string& name) {// 實際實現中通過動態庫加載if (name == "A") return std::make_unique<PluginA>();if (name == "B") return std::make_unique<PluginB>();return nullptr;}
};// 運行時加載插件
void loadPluginAtRuntime(const std::string& pluginName) {auto plugin = PluginFactory::createPlugin(pluginName);if (plugin) plugin->execute();
}
三、各方案對比
方案 | 實現方式 | 綁定時機 | 靈活性 | 復雜度 |
---|---|---|---|---|
組合替代繼承 | 持有基類指針 | 運行時 | 高 | 中 |
模板 | 模板參數選擇基類 | 編譯時 | 中 | 低 |
多重繼承 | 繼承所有基類,選接口使用 | 運行時 | 中 | 高 |
動態加載插件 | 運行時加載動態庫 | 運行時 | 極高 | 高 |
四、總結
- C++無法真正實現運行時繼承,因為繼承關系必須在編譯時確定。
- 組合(策略模式)是最常用的替代方案,它通過委托機制在運行時動態選擇行為。
- 模板方案適用于編譯時已知的選擇,可提供零成本抽象。
- 動態加載插件則適用于需要最大靈活性的場景(如插件系統)。
根據具體需求,選擇合適的替代方案來模擬“運行時選擇基類”的行為。