概述
????????訪問者模式允許我們在不改變類的前提下,向已有類添加新的功能。簡單來說,就是將算法與對象的數據結構進行分離的一種方法。在實際應用中,當我們需要對一組對象執行一些操作,而這些操作又需要隨著需求的變化而不斷變化時,訪問者模式就顯得尤為重要了。
????????電子商務平臺的庫存管理系統是現實生活中運用訪問者模式的一個典型例子。電子商務平臺會銷售不同種類的商品,比如:書籍、電子產品和服裝等。我們需要定期對庫存進行不同的統計分析,包括:計算總價值、統計數量等。隨著業務的發展,可能會有新的商品類型加入,也可能會有新的統計需求出現。訪問者模式允許我們將特定的商品處理邏輯與商品類本身分離,使得添加新的商品類型或處理邏輯變得更為簡單和靈活。
基本原理
????????訪問者模式的核心思想是:將算法與對象結構分離。具體來說,訪問者模式通過定義一個操作(通常稱為“訪問”),這個操作可以在不修改該元素的類的前提下,為每一個具體元素類聲明一個該操作。在訪問者模式中,我們有兩個主要的角色:一個是接受訪問的對象集合(即元素),另一個是對這些對象執行操作的訪問者。元素知道如何接受訪問者,并且會調用訪問者的相應方法來完成操作。
????????訪問者模式主要由以下五個核心組件構成。
????????1、訪問者。為每一個具體元素聲明一個訪問操作,表示訪問者訪問一個元素所要完成的工作。通常情況下,訪問者會包含多個Visit方法,每個方法對應一種具體的元素類型。
????????2、具體訪問者。實現了訪問者接口中的每種Visit方法,以完成具體的業務邏輯。
????????3、元素。定義了一個接受訪問者的接口Accept,其主要作用是讓訪問者訪問自身。同時,Element也是一個抽象類,代表了一組可以被訪問的對象。
????????4、具體元素。通常是那些需要被訪問的對象,實現了Element接口,并提供自身的具體實現。
????????5、對象結構。管理元素對象的集合,提供了遍歷元素的方法。
????????基于上面的核心組件,訪問者模式的實現主要有以下五個步驟。
????????1、定義元素接口。創建一個抽象類或接口,至少聲明了一個Accept方法。該方法用于接收一個訪問者對象,并調用訪問者的相應Visit方法來執行對當前元素的操作。
????????2、創建具體元素類。對于每一個需要被訪問的具體元素,創建一個繼承自元素接口的類。在每個具體元素類中,實現Accept方法,使其調用傳入的訪問者對象的對應Visit方法,并傳遞自身作為參數。
????????3、定義訪問者接口。創建一個接口,為每種具體訪問者類型定義一個Visit方法。這意味著,每當添加一個新的具體訪問者類型時,都需要向訪問者接口中添加一個新的Visit方法聲明。
????????4、實現具體訪問者類。根據具體的業務邏輯需求,創建實現訪問者接口的具體訪問者類。在這些類中,實現所有聲明的Visit方法,每個方法都包含了針對特定訪問者類型的處理邏輯。
????????5、創建對象結構。創建一個管理元素集合的對象結構,可以是一個簡單的容器,也可以是更復雜的數據結構。此對象結構應提供遍歷其內部元素的方法,并能夠接受一個訪問者對象,依次對其內部的每個元素調用Accept方法。
實戰代碼
????????在下面的實戰代碼中,我們使用訪問者模式模擬了電子商務平臺庫存管理系統的實現。
????????首先,我們定義了一個抽象基類CProduct,它包含商品的基本信息(名稱、價格、庫存量),并聲明了一個純虛函數Accept用于接收訪問者對象。
????????接著,我們定義了兩個具體的商品類CBook和CElectronics。它們繼承自CProduct,并實現了Accept方法。該方法調用傳入的訪問者的相應Visit方法,以執行針對具體商品類型的特定操作。
????????然后,我們定義了一個抽象訪問者類CVisitor。其中聲明了針對每種商品類型的Visit方法,并通過具體訪問者類CStockValueCalculator實現了這些方法,用來計算庫存總價值。此外,還有一個管理商品集合的對象結構類CInventory。它負責存儲商品實例,并提供了一個PerformCalculations方法遍歷所有商品,對每個商品調用其Accept方法,傳入具體的訪問者對象以執行相應的業務邏輯。
????????最后,在main函數中,我們創建了一個CInventory實例。在添加了幾種不同類型的商品后,我們使用CStockValueCalculator訪問者來計算庫存總價值,并最終輸出了結果。
#include <iostream>
#include <vector>
#include <string>using namespace std;class CVisitor;// 元素接口
class CProduct
{
public:CProduct(const string& name, double price, int stock) : m_strName(name), m_dbPrice(price), m_nStock(stock) {}virtual ~CProduct() {}virtual void Accept(CVisitor& visitor) = 0;string GetName() const { return m_strName; }double GetPrice() const { return m_dbPrice; }int GetStock() const { return m_nStock; }protected:string m_strName;double m_dbPrice;int m_nStock;
};// 具體元素:書籍
class CBook : public CProduct
{
public:CBook(const string& name, double price, int stock) : CProduct(name, price, stock) {}void Accept(CVisitor& visitor) override;
};// 具體元素:電子產品
class CElectronics : public CProduct
{
public:CElectronics(const string& name, double price, int stock) : CProduct(name, price, stock) {}void Accept(CVisitor& visitor) override;
};// 訪問者接口
class CVisitor
{
public:virtual ~CVisitor() {}virtual void Visit(CBook& book) = 0;virtual void Visit(CElectronics& electronics) = 0;
};// 具體訪問者:計算庫存總價值
class CStockValueCalculator : public CVisitor
{
public:void Visit(CBook& book) override{m_dbTotalValue += book.GetPrice() * book.GetStock();}void Visit(CElectronics& electronics) override{m_dbTotalValue += electronics.GetPrice() * electronics.GetStock();}double GetTotalValue() const { return m_dbTotalValue; }private:double m_dbTotalValue = 0.0;
};void CBook::Accept(CVisitor& visitor)
{visitor.Visit(*this);
}void CElectronics::Accept(CVisitor& visitor)
{visitor.Visit(*this);
}// 對象結構,用于管理商品集合
class CInventory
{
public:~CInventory(){for (CProduct* product : m_vctProduct){delete product;}}void AddProduct(CProduct* product){m_vctProduct.push_back(product);}void PerformCalculations(CVisitor& visitor){for (CProduct* product : m_vctProduct){product->Accept(visitor);}}private:vector<CProduct*> m_vctProduct;
};int main()
{CInventory inventory;inventory.AddProduct(new CBook("Effective C++", 50.0, 10));inventory.AddProduct(new CElectronics("Phone", 1999.99, 20));CStockValueCalculator calculator;inventory.PerformCalculations(calculator);cout << "Total stock value: " << calculator.GetTotalValue() << endl;return 0;
}
總結
????????通過將算法從對象結構中分離出來,訪問者模式使得每個角色(元素和訪問者)都只負責自己的部分。另外,訪問者模式使得添加新的操作變得容易,而不需要修改現有的類。只需創建一個新的訪問者類來實現所需的操作即可,無需改動已有的元素類。
????????但引入訪問者模式會增加系統的設計復雜度,特別是當對象結構中有大量不同類型元素時,需要為每種類型定義相應的Visit方法,增加了代碼量和理解難度。訪問者需要知道所有被訪問元素的具體類型和內部表示,這可能會導致訪問者與元素之間產生緊密耦合,從而破壞了封裝性。