前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python设计模式(11):访问者模式

Python设计模式(11):访问者模式

作者头像
不可言诉的深渊
发布2019-07-26 17:34:21
7790
发布2019-07-26 17:34:21
举报
文章被收录于专栏:Python机器学习算法说书人

在软件设计中,经常会遇到对系统中一个已经完成设计与代码编写的类的层次结构进行功能修改或增加新功能的情况,这就需要对该层次结构的某些类进行修改。如果改动量较小尚可,但是往往这种变动会经常发生,且任何变动都可能导致整个类层次结构中的大多数类都随之进行修改。此时,最好考虑对这个层次类重构。即将介绍的访问者模式(Visitor Pattern)可以用来解决类似的问题。访问者模式在处理数据结构较稳定,但是作用于其上的操作需要经常变化的问题时是非常有效的。

访问者模式是指作用于一个对象结构体上的元素的操作。访问者可以使用户在不改变该结构体中的类的基础上定义一个新的操作。

访问者模式的类图如图所示。

该类图包含两个系列的类:一个是 Element 类,另外一个是访问者类。访问者类定义了施加于 Element 类上的操作,为 Element 类提供一些功能。可以有多种具体的访问者类,各自完成特定的目的,如一个访问者类是计算价格,另一个访问者类则是计算存货数量。因此需要定义一个抽象的访问者父类 Visitor 以及用于各种特殊目的具体的子类。Visitor 类必须给每个结点类提供一个操作,即访问方法,例如获得各结点所代表的商品对象的价格等。

访问者模式的各组成部分及说明如下。

  1. Visitor:为每个 Element 的对象声明一个访问操作。该访问操作的名字最好要包含被访问的类的名字,以便确认该访问操作是专门针对哪个具体的类,如 visitFamilyNoChildren 是专门为了服务类 FamilyNoChildren 的。
  2. ConcreteVisitor:实现 Visitor 声明的运算。每个运算实现为对应的类的对象定义的算法的一部分。ConcreteVisitor 提供算法的环境并且存储其局部状态。
  3. Element:定义了一些基本的方法,其中包含提供基本数据的方法,例如一些 get()与 set()方法。重要的是,每个 Element 子类都必须定义一个接收者方法,该方法以 Visitor 为参数类型:Accept(Visitor),其作用是为被访问者对象和访问者对象之间的交互提供接口。
  4. ConcreteElement:具体的 Element 的子类,例如 ElementA,该类包含一个 accept 方法接收访问者对象。另外,该类还可能定义一些其他的方法以帮助访问者实现一些功能。
  5. ObjectStructure:提供一个高层接口,允许访问者访问 Element 的子类。在该类中可以包含一个结构,例如 ArrayList、Vector 等,提供所要访问的 element 的列表。

在以下情况可以使用访问者模式。

  1. 当一个对象的结构中,包含有多种类型的具有不同接口的对象,且用户要在这些对象上进行依赖于具体的类的运算时,需要用到访问者模式。这就是为什么访问者模式要针对每个被访问的子类都设计一个不同的接口的原因。事实上,如果每个被访问的子类都有相同的接口,包括构造方法、其他方法、参数都一致,则访问者类只需要设计一个访问方法,在该方法中含有一个用于区别不同的被访问的子类的参数即可,例如可以使用被访问者基类作为参数类型。在对象的结构中包含有多种类型的有不同接口的对象时,各个不同的访问方法可能为访问所对应的类提供不同的参数类型。
  2. 当有多个不同的并且互不相关的运算将作用到这些对象上,而用户不希望这些运算混淆这些类时,可以使用访问者模式将相关的操作放到独立的类中,例如为了实现每个结点类中的计算价格方法,可以将所有的计算价格方法放到一个 VisitPrice 类中。
  3. 在对象的数据类型很少改变,但是需要经常改变操作或者增加新的操作的情况下,可以使用访问者模式。反之,如果 Element 的子类经常改变结构,例如需要增加一个新的税种,这就需要在访问者类中增加新的访问方法,因此,在这种情况下使用访问者模式代价较高,尽量不要使用访问者模式。

访问者模式的优点如下。

  1. 使得在访问者类中针对复杂类结构中的某个类添加新方法较为容易,即只需要简单地添加一个新的访问者方法即可。如果不采用访问者模式,这需要在每个类中添加一个新的方法。
  2. 访问者将相关的方法集中在一个具体的访问者类中,而其他相关的方法集中在另外一个具体的访问者类中。也就是说,访问者子类是按照方法的类型来分类的。

访问者模式的缺点是,增加一个具体的新 ConcreteElement 类比较困难。因为此时需要在每一个 ConcreteVisitor 类中添加该 ConcreteElement 类的访问方法。

代码语言:javascript
复制
from abc import ABC, abstractmethod


class Visitor(ABC):
    @abstractmethod
    def visit_element_a(self, element_a):
        pass

    @abstractmethod
    def visit_element_b(self, element_b):
        pass


class ConcreteVisitor1(Visitor):
    def visit_element_a(self, element_a):
        print('ConcreteVisitor1', 'element_a')

    def visit_element_b(self, element_b):
        print('ConcreteVisitor1', 'element_b')


class ConcreteVisitor2(Visitor):
    def visit_element_a(self, element_a):
        print('ConcreteVisitor2', 'element_a')

    def visit_element_b(self, element_b):
        print('ConcreteVisitor2', 'element_b')


class Element(ABC):
    @abstractmethod
    def accept(self, visitor):
        pass


class ElementA(Element):
    def accept(self, visitor):
        visitor.visit_element_a(self)


class ElementB(Element):
    def accept(self, visitor):
        visitor.visit_element_b(self)


class ObjectStructure:
    def __init__(self):
        self.elements = [ElementA(), ElementB()]


class Client:
    @staticmethod
    def main():
        object_structure = ObjectStructure()
        visitor = ConcreteVisitor1()
        for element in object_structure.elements:
            element.accept(visitor)
        visitor = ConcreteVisitor2()
        for element in object_structure.elements:
            element.accept(visitor)


if __name__ == '__main__':
    Client.main()
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-03-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python机器学习算法说书人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档