• 设计模式 22 访问者模式 Visitor Pattern


    设计模式 22 访问者模式 Visitor Pattern

    1.定义


    访问者模式是一种行为型设计模式,它允许你在不改变已有类结构的情况下,为一组对象添加新的操作。它将算法与对象结构分离,使你能够在不修改现有类的情况下,为这些类添加新的操作。

    2.内涵

    访问者模式核心概念,有以下几点:

    1. 访问者 (Visitor): 定义了一个访问具体元素的方法,每个方法对应一个具体元素类。
    2. 具体访问者 (ConcreteVisitor): 实现访问者接口,定义对具体元素类的访问逻辑。
    3. 元素 (Element): 定义一个 accept 方法,接受访问者对象并调用访问者的 visit 方法。
    4. 具体元素 (ConcreteElement): 实现元素接口,提供访问者访问其内部状态的方法。

    相关UML 图如下所示:

            

    解释:

    • Visitor 接口:定义了 visit 方法,接受一个 Element 对象作为参数。
    • ConcreteVisitor 类:实现了 Visitor 接口,并定义了对 Element 对象的具体操作逻辑。
    • Element 接口:定义了 accept 方法,接受一个 Visitor 对象作为参数,并调用访问者的 visit 方法。
    • ConcreteElement 类:实现了 Element 接口,并提供 operation 方法,用于访问其内部状态。

    3.案例分析
    1. /**
    2.  * The Visitor Interface declares a set of visiting methods that correspond to
    3.  * component classes. The signature of a visiting method allows the visitor to
    4.  * identify the exact class of the component that it's dealing with.
    5.  */
    6. class ConcreteComponentA;
    7. class ConcreteComponentB;
    8. class Visitor {
    9.  public:
    10.   virtual void VisitConcreteComponentA(const ConcreteComponentA *element) const = 0;
    11.   virtual void VisitConcreteComponentB(const ConcreteComponentB *element) const = 0;
    12. };
    13. /**
    14.  * The Component interface declares an `accept` method that should take the base
    15.  * visitor interface as an argument.
    16.  */
    17. class Component {
    18.  public:
    19.   virtual ~Component() {}
    20.   virtual void Accept(Visitor *visitor) const = 0;
    21. };
    22. /**
    23.  * Each Concrete Component must implement the `Accept` method in such a way that
    24.  * it calls the visitor's method corresponding to the component's class.
    25.  */
    26. class ConcreteComponentA : public Component {
    27.   /**
    28.    * Note that we're calling `visitConcreteComponentA`, which matches the
    29.    * current class name. This way we let the visitor know the class of the
    30.    * component it works with.
    31.    */
    32.  public:
    33.   void Accept(Visitor *visitor) const override {
    34.     visitor->VisitConcreteComponentA(this);
    35.   }
    36.   /**
    37.    * Concrete Components may have special methods that don't exist in their base
    38.    * class or interface. The Visitor is still able to use these methods since
    39.    * it's aware of the component's concrete class.
    40.    */
    41.   std::string ExclusiveMethodOfConcreteComponentA() const {
    42.     return "A";
    43.   }
    44. };
    45. class ConcreteComponentB : public Component {
    46.   /**
    47.    * Same here: visitConcreteComponentB => ConcreteComponentB
    48.    */
    49.  public:
    50.   void Accept(Visitor *visitor) const override {
    51.     visitor->VisitConcreteComponentB(this);
    52.   }
    53.   std::string SpecialMethodOfConcreteComponentB() const {
    54.     return "B";
    55.   }
    56. };
    57. /**
    58.  * Concrete Visitors implement several versions of the same algorithm, which can
    59.  * work with all concrete component classes.
    60.  *
    61.  * You can experience the biggest benefit of the Visitor pattern when using it
    62.  * with a complex object structure, such as a Composite tree. In this case, it
    63.  * might be helpful to store some intermediate state of the algorithm while
    64.  * executing visitor's methods over various objects of the structure.
    65.  */
    66. class ConcreteVisitor1 : public Visitor {
    67.  public:
    68.   void VisitConcreteComponentA(const ConcreteComponentA *element) const override {
    69.     std::cout << element->ExclusiveMethodOfConcreteComponentA() << " + ConcreteVisitor1\n";
    70.   }
    71.   void VisitConcreteComponentB(const ConcreteComponentB *element) const override {
    72.     std::cout << element->SpecialMethodOfConcreteComponentB() << " + ConcreteVisitor1\n";
    73.   }
    74. };
    75. class ConcreteVisitor2 : public Visitor {
    76.  public:
    77.   void VisitConcreteComponentA(const ConcreteComponentA *element) const override {
    78.     std::cout << element->ExclusiveMethodOfConcreteComponentA() << " + ConcreteVisitor2\n";
    79.   }
    80.   void VisitConcreteComponentB(const ConcreteComponentB *element) const override {
    81.     std::cout << element->SpecialMethodOfConcreteComponentB() << " + ConcreteVisitor2\n";
    82.   }
    83. };
    84. /**
    85.  * The client code can run visitor operations over any set of elements without
    86.  * figuring out their concrete classes. The accept operation directs a call to
    87.  * the appropriate operation in the visitor object.
    88.  */
    89. void ClientCode(std::array<const Component *, 2> components, Visitor *visitor) {
    90.   // ...
    91.   for (const Component *comp : components) {
    92.     comp->Accept(visitor);
    93.   }
    94.   // ...
    95. }
    96. int main() {
    97.   std::array<const Component *, 2> components = {new ConcreteComponentA, new ConcreteComponentB};
    98.   std::cout << "The client code works with all visitors via the base Visitor interface:\n";
    99.   ConcreteVisitor1 *visitor1 = new ConcreteVisitor1;
    100.   ClientCode(components, visitor1);
    101.   std::cout << "\n";
    102.   std::cout << "It allows the same client code to work with different types of visitors:\n";
    103.   ConcreteVisitor2 *visitor2 = new ConcreteVisitor2;
    104.   ClientCode(components, visitor2);
    105.   for (const Component *comp : components) {
    106.     delete comp;
    107.   }
    108.   delete visitor1;
    109.   delete visitor2;
    110.   return 0;
    111. }

    输出:

    1. The client code works with all visitors via the base Visitor interface:
    2. A + ConcreteVisitor1
    3. B + ConcreteVisitor1
    4. It allows the same client code to work with different types of visitors:
    5. A + ConcreteVisitor2
    6. B + ConcreteVisitor2

    上述代码类之间关系 UML图,如下所示


    4.注意事项

    访问者模式在实施过程中,需要考虑以下几个方面,以确保代码的正确性、可维护性和可扩展性:

    1. 元素接口的设计:

    元素接口应该定义一个 accept 方法,用于接受访问者对象。
    accept 方法应该调用访问者的 visit 方法,并将自身作为参数传递给 visit 方法。
    元素接口的设计应该尽可能通用,以支持不同类型的元素。


    2. 访问者接口的设计:

    访问者接口应该定义一个或多个 visit 方法,每个方法对应一个具体元素类。
    visit 方法应该接受对应元素类对象作为参数,并执行对该元素类的操作。
    访问者接口的设计应该尽可能灵活,以支持不同的操作。


    3. 具体访问者类的实现:

    每个具体访问者类应该实现访问者接口,并定义对不同元素类的操作逻辑。
    具体访问者类的实现应该尽可能清晰、简洁,以提高代码可读性和可维护性。


    4. 访问者模式的应用场景:

    访问者模式适用于需要为一组对象添加新的操作,而不想修改这些对象的类的情况。
    访问者模式也适用于需要对一组对象进行不同的操作,而这些操作之间没有明显的关联的情况。
    访问者模式还可以用于将算法逻辑与对象结构分离,提高代码的可读性和可维护性。


    5. 访问者模式的局限性:

    访问者模式可能会增加代码的复杂性,尤其是当需要处理多种类型的元素时。
    访问者模式可能会破坏元素类的封装性,因为访问者需要访问元素类的内部状态。

    5.最佳实践


    访问者模式是一种强大的工具,但使用不当会导致代码复杂化或破坏封装性。以下是一些最佳实践,帮助你有效地使用访问者模式:

    1. 限制访问者数量:

    避免过度使用访问者,只在需要为一组对象添加新操作,且不希望修改这些对象本身时使用。
    尽量保持访问者数量较少,否则会增加代码复杂度,难以维护。


    2. 保持访问者职责单一:

    每个访问者应该只负责一种操作,避免将多个操作混杂在一个访问者中。
    职责单一的访问者更容易理解和维护,也更容易进行扩展。


    3. 避免访问者修改元素状态:

    访问者应该主要负责执行操作,而不是修改元素状态。
    如果需要修改元素状态,应该通过元素本身的方法来进行,而不是通过访问者。


    4. 使用泛型或模板:

    对于需要处理多种类型元素的访问者,可以使用泛型或模板来简化代码。
    泛型或模板可以避免重复代码,提高代码可读性和可维护性。


    5. 考虑使用组合模式:

    如果需要对多个层次结构的元素进行操作,可以考虑将访问者模式与组合模式结合使用。
    组合模式可以帮助你构建树形结构,而访问者模式可以帮助你遍历树形结构并执行操作。


    6. 谨慎使用访问者模式:

    访问者模式并非万能的,在某些情况下,其他设计模式可能更适合。
    仔细分析你的需求,选择最合适的模式来解决问题。


    6.总结

            访问者模式是一种强大的设计模式,但需要谨慎使用。在实施访问者模式时,需要仔细考虑元素接口和访问者接口的设计,以及具体访问者类的实现。同时,需要了解访问者模式的局限性,并选择合适的应用场景。

  • 相关阅读:
    P1004 [NOIP2000 提高组] 方格取数
    Java中的Spring Cloud:微服务与注册中心
    c++备忘录
    前台主页搭建、后台主页轮播图接口设计、跨域问题详解、前后端互通、后端自定义配置、git软件的初步介绍
    算法刷题打卡第28天:省份数量---广度优先搜索
    EFLK日志收集
    postman-pre-request-scripts使用
    ubuntu部署若依vue版
    ElasticSearch自学笔记
    DUBBO中的一致性哈希算法
  • 原文地址:https://blog.csdn.net/jxusthusiwen/article/details/139277486