实战设计模式之访问者模式

打印 上一主题 下一主题

主题 1870|帖子 1870|积分 5610

概述

        访问者模式允许我们在不改变类的前提下,向已有类添加新的功能。简单来说,就是将算法与对象的数据结构进行分离的一种方法。在实际应用中,当我们需要对一组对象执行一些操纵,而这些操纵又需要随着需求的变化而不断变化时,访问者模式就显得尤为紧张了。
        电子商务平台的库存管理体系是现实生存中运用访问者模式的一个范例例子。电子商务平台会贩卖不同种类的商品,比如:书籍、电子产品和服装等。我们需要定期对库存进行不同的统计分析,包括:计算总代价、统计数目等。随着业务的发展,大概会有新的商品范例加入,也大概会有新的统计需求出现。访问者模式允许我们将特定的商品处置惩罚逻辑与商品类本身分离,使得添加新的商品范例或处置惩罚逻辑变得更为简单和机动。


基本原理

        访问者模式的核心头脑是:将算法与对象结构分离。具体来说,访问者模式通过定义一个操纵(通常称为“访问”),这个操纵可以在不修改该元素的类的前提下,为每一个具体元素类声明一个该操纵。在访问者模式中,我们有两个主要的角色:一个是接受访问的对象聚集(即元素),另一个是对这些对象执行操纵的访问者。元素知道如何接受访问者,并且会调用访问者的相应方法来完成操纵。
        访问者模式主要由以下五个核心组件构成。
        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访问者来计算库存总代价,并终极输出了结果。
  1. #include <iostream>
  2. #include <vector>
  3. #include <string>
  4. using namespace std;
  5. class CVisitor;
  6. // 元素接口
  7. class CProduct
  8. {
  9. public:
  10.     CProduct(const string& name, double price, int stock) :
  11.         m_strName(name), m_dbPrice(price), m_nStock(stock) {}
  12.     virtual ~CProduct() {}
  13.     virtual void Accept(CVisitor& visitor) = 0;
  14.     string GetName() const { return m_strName; }
  15.     double GetPrice() const { return m_dbPrice; }
  16.     int GetStock() const { return m_nStock; }
  17. protected:
  18.     string m_strName;
  19.     double m_dbPrice;
  20.     int m_nStock;
  21. };
  22. // 具体元素:书籍
  23. class CBook : public CProduct
  24. {
  25. public:
  26.     CBook(const string& name, double price, int stock) :
  27.         CProduct(name, price, stock) {}
  28.     void Accept(CVisitor& visitor) override;
  29. };
  30. // 具体元素:电子产品
  31. class CElectronics : public CProduct
  32. {
  33. public:
  34.     CElectronics(const string& name, double price, int stock) :
  35.         CProduct(name, price, stock) {}
  36.    
  37.     void Accept(CVisitor& visitor) override;
  38. };
  39. // 访问者接口
  40. class CVisitor
  41. {
  42. public:
  43.     virtual ~CVisitor() {}
  44.     virtual void Visit(CBook& book) = 0;
  45.     virtual void Visit(CElectronics& electronics) = 0;
  46. };
  47. // 具体访问者:计算库存总价值
  48. class CStockValueCalculator : public CVisitor
  49. {
  50. public:
  51.     void Visit(CBook& book) override
  52.     {
  53.         m_dbTotalValue += book.GetPrice() * book.GetStock();
  54.     }
  55.     void Visit(CElectronics& electronics) override
  56.     {
  57.         m_dbTotalValue += electronics.GetPrice() * electronics.GetStock();
  58.     }
  59.     double GetTotalValue() const { return m_dbTotalValue; }
  60. private:
  61.     double m_dbTotalValue = 0.0;
  62. };
  63. void CBook::Accept(CVisitor& visitor)
  64. {
  65.     visitor.Visit(*this);
  66. }
  67. void CElectronics::Accept(CVisitor& visitor)
  68. {
  69.     visitor.Visit(*this);
  70. }
  71. // 对象结构,用于管理商品集合
  72. class CInventory
  73. {
  74. public:
  75.     ~CInventory()
  76.     {
  77.         for (CProduct* product : m_vctProduct)
  78.         {
  79.             delete product;
  80.         }
  81.     }
  82.     void AddProduct(CProduct* product)
  83.     {
  84.         m_vctProduct.push_back(product);
  85.     }
  86.     void PerformCalculations(CVisitor& visitor)
  87.     {
  88.         for (CProduct* product : m_vctProduct)
  89.         {
  90.             product->Accept(visitor);
  91.         }
  92.     }
  93. private:
  94.     vector<CProduct*> m_vctProduct;
  95. };
  96. int main()
  97. {
  98.     CInventory inventory;
  99.     inventory.AddProduct(new CBook("Effective C++", 50.0, 10));
  100.     inventory.AddProduct(new CElectronics("Phone", 1999.99, 20));
  101.     CStockValueCalculator calculator;
  102.     inventory.PerformCalculations(calculator);
  103.     cout << "Total stock value: " << calculator.GetTotalValue() << endl;
  104.     return 0;
  105. }
复制代码

总结

        通过将算法从对象结构中分离出来,访问者模式使得每个角色(元素和访问者)都只负责本身的部门。别的,访问者模式使得添加新的操纵变得容易,而不需要修改现有的类。只需创建一个新的访问者类来实现所需的操纵即可,无需改动已有的元素类。
        但引入访问者模式会增加体系的设计复杂度,特别是当对象结构中有大量不同范例元素时,需要为每种范例定义相应的Visit方法,增加了代码量和理解难度。访问者需要知道全部被访问元素的具体范例和内部表示,这大概会导致访问者与元素之间产生紧密耦合,从而破坏了封装性。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

汕尾海湾

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表