假设要实现一个存放多种类型数据结构的对象,比如一个存放算术操作数和操作符的树结点,需要存放包含一元操作符、二元操作符和数字类型的结点

class Node:passclass UnaryOperator(Node):def __init__(self, operand):self.operand = operandclass BinaryOperator(Node):def __init__(self, left, right):self.left = leftself.right = rightclass Add(BinaryOperator):passclass Sub(BinaryOperator):passclass Mul(BinaryOperator):passclass Div(BinaryOperator):passclass Negative(UnaryOperator):passclass Number(Node):def __init__(self, value):self.value = value

执行运算需要这样调用

# 假设运算式子:2 - (2+2) * 2 / 1 = 2-(8) = -6.0
t1 = Add(Number(2), Number(2))
t2 = Mul(t1, Number(2))
t3 = Div(t2, Number(1))
t4 = Sub(Number(2), t3)

或者这样调用

t5 = Sub(Number(2), Div(Mul(Add(Number(2), Number(2)), Number(2)), Number(1)))

这样子需要执行多次类的调用,极不易读写且冗长,有没有一种方法让调用更加通用,访问变得简单呢。这里使用访问者模式可以达到这样的目的。

访问者模式能够在不改变元素所属对象结构的情况下操作元素,让调用或调用者(caller)的方式变得简单,这种操作常见于的士公司操作,当一个乘客叫了一辆的士时,的士公司接收到了一个访问者,并分配一辆的士去接这个乘客。

首先定义一个访问者结点类VisitorNode,实现最基本的访问入口,任何访问的方式都需要继承这个访问者结点类,并通过这个访问者结点类的visit()方法来访问它的各种操作

# 访问者节点的基类
class NodeVisitor:def visit(self, node):if not isinstance(node, Node):  # 不是Node对象时当做一个值返回,如果有其他情况可以根据实际来处理return nodeself.meth = "visit_" + type(node).__name__.lower()  # type(node)也可以换成node.__class__(只要node.__class__不被篡改)meth = getattr(self, self.meth, None)  if meth is None:meth = self.generic_visitreturn meth(node)def generic_visit(self, node):raise RuntimeError(f"No {self.meth} method")# (一种)访问者对应的类
class Visitor(NodeVisitor):"""方法的名称定义都要与前面定义过的结点类(Node)的名称保证一致性"""def visit_add(self, node):return self.visit(node.left) + self.visit(node.right)def visit_sub(self, node):return self.visit(node.left) - self.visit(node.right)def visit_mul(self, node):return self.visit(node.left) * self.visit(node.right)def visit_div(self, node):return self.visit(node.left) / self.visit(node.right)def visit_negative(self, node):  # 如果class Negative 命名-> class Neg,那么 def visit_negative 命名-> def visit_negreturn -self.visit(node.operand)def visit_number(self, node):return node.value

这里的meth = getattr(self, self.meth, None)使用了字符串调用对象方法,self.meth动态地根据各类Node类(Add, Sub, Mul…)的名称定义了对应于类Visitor中的方法(visit_add, visit_sub, visit_mul…)简化了访问入口的代码,当没有获取到对应的方法时会执行generic_visit()并抛出RuntimeError的异常提示访问过程中的异常

如果需要添加一种操作,比如取绝对值,只需要定义一个类class Abs(Unaryoperator): pass并在类Visitor中定义一个visit_abs(self, node)方法即可,不需要做出任何多余的修改,更不需要改变存储的结构

这里visit()方法调用了visit_xxx()方法,而visit_xxx()可能也调用了visit(),本质上是visit()的循环递归调用,当数据量变大时,效率会变得很慢,且递归层次过深时会导致超过限制而失败,而下面介绍的就是利用栈和生成器来消除递归提升效率的实现访问者模式的方法

import typesclass Node:passclass BinaryOperator(Node):def __init__(self, left, right):self.left = leftself.right = rightclass UnaryOperator(Node):def __init__(self, operand):self.operand = operandclass Add(BinaryOperator):passclass Sub(BinaryOperator):passclass Mul(BinaryOperator):passclass Div(BinaryOperator):passclass Negative(UnaryOperator):passclass Number(Node):def __init__(self, value):  # 与UnaryOperator区别仅命名不同self.value = valueclass NodeVisitor:def visit(self, node):# 使用栈+生成器来替换原来visit()的递归写法stack = [node]last_result = None  # 执行一个操作最终都会返回一个值while stack:last = stack[-1]try:if isinstance(last, Node):stack.append(self._visit(stack.pop()))elif isinstance(last, types.GeneratorType):   # GeneratorType会是上一个if返回的对象,这个对象会返回两个node执行算术之后的结果# 如果是生成器,不pop掉,而是不断send,直到StopIteration# 如果last_result不是None,这个值会给回到生成器(例如2被visit_add()的左值接收到)stack.append(last.send(last_result))last_result = Noneelse:   # 计算结果是一个值last_result = stack.pop()except StopIteration:   # 生成器yield结束stack.pop()return last_resultdef _visit(self, node):self.method_name = "visit_" + type(node).__name__.lower()method = getattr(self, self.method_name, None)if method is None:self.generic_visit(node)return method(node)def generic_visit(self, node):raise RuntimeError(f"No {self.method_name} method")class Visitor(NodeVisitor):def visit_add(self, node):yield (yield node.left) + (yield node.right)    # node.left和node.right都可能是Nodedef visit_sub(self, node):yield (yield node.left) - (yield node.right)def visit_mul(self, node):yield (yield node.left) * (yield node.right)def visit_div(self, node):yield (yield node.left) / (yield node.right)def visit_negative(self, node):yield -(yield node.operand)def visit_number(self, node):return node.value

测试是否还会引起超过递归层数的异常

def test_time_cost():import times = time.perf_counter()a = Number(0)for n in range(1, 100000):a = Add(a, Number(n))v = Visitor()print(v.visit(a))print(f"time cost:{time.perf_counter() - s}")

输出正常,没有问题

4999950000
time cost:0.9547078


最后琢磨出了一个似乎可以作为替代的方法

class Node:passclass UnaryOperator(Node):def __init__(self, operand):self.operand = operandclass BinaryOperator(Node):def __init__(self, left, right):self.left = leftself.right = rightclass Add(BinaryOperator):def __init__(self, left, right):super().__init__(left, right)self.value = self.left.value + self.right.valuepassclass Sub(BinaryOperator):def __init__(self, left, right):super().__init__(left, right)self.value = self.left.value - self.right.valuepassclass Mul(BinaryOperator):def __init__(self, left, right):super().__init__(left, right)self.value = self.left.value * self.right.valuepassclass Div(BinaryOperator):def __init__(self, left, right):super().__init__(left, right)self.value = self.left.value / self.right.valuepassclass Negative(UnaryOperator):def __init__(self, operand):super().__init__(operand)self.value = -self.operand.valuepassclass Number(Node):def __init__(self, value):self.value = value

运行测试

def test_time_cost():import times = time.perf_counter()a = Number(0)for n in range(1, 100000):a = Add(a, Number(n))print(a.value)print(time.perf_counter() - s)

输出

4999950000
0.2506986

比前面的访问者模式还快而且不用递归,哈哈,焯,算了,写都写了

Python实现访问者模式相关推荐

  1. python的编程模式-举例讲解Python设计模式编程中的访问者与观察者模式

    访问者模式我觉得Visitor模式是在补修改已有程序结构前提下,通过添加额外的访问者完成对代码功能的拓展 为什么这样用?当你的类层次较多,在某层结构中增加新的方法,要是在基类上面添加或者变更,可能破坏 ...

  2. 设计模式(行为型模式)——访问者模式(Visitor)

    2019独角兽企业重金招聘Python工程师标准>>> 访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化.访问者模式适用于数据结构相对稳定算法又易变化的系 ...

  3. OOP 中的 方法调用、接口、鸭式辩型、访问者模式

    2019独角兽企业重金招聘Python工程师标准>>> 方法调用的四种方式 直接调用:通过类或者实例直接调用其方法. 接口调用或者转型调用:通过将实例回调给一个接口对象,或者转型为一 ...

  4. github private链接访问_Hands-On Design Patterns With C++(十八)访问者模式与多分派(下)...

    本文是本书最后一篇文章,完结撒花!谢谢大家观看! 目录: trick:Hands-On Design Patterns With C++(零)前言​zhuanlan.zhihu.com 访问者模式与多 ...

  5. 行为型模式:访问者模式

    前方高能:<一故事一设计模式>PDF 电子书已经上线,关注公众号即可获取. 文章首发: 行为型模式:访问者模式 十一大行为型模式之十一:访问者模式. 简介 姓名 :访问者模式 英文名 :V ...

  6. 基于设计模式的学习之旅-----访问者模式(附源码)

    基于设计模式的学习之旅-----访问者模式 1.初始访问者模式 2.什么是访问者模式 表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 3.模 ...

  7. 设计模式之访问者模式(Visitor)摘录

    23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...

  8. Python设计模式-建造者模式

    Python设计模式-建造者模式 代码基于3.5.2,代码如下; #coding:utf-8 #建造者模式 class Burger():name = ""price = 0.0d ...

  9. Python设计模式-状态模式

    Python设计模式-状态模式 代码基于3.5.2,代码如下; #coding:utf-8 #状态模式class state():def writeProgram(self,work):raise N ...

最新文章

  1. 获取前台HTML控件的值(select)
  2. 魅族2016Java互联网方向其中一道笔试题--青蛙跳台阶问题
  3. JSP报表打印的一种简单解决方案
  4. Vue框架实例成员及项目搭建
  5. win7动态壁纸_壁纸软件推荐-wallpaper engine
  6. Windows python2.7虚拟环境下的PyV8安装
  7. iPhone 11 PRO 820-01508-10, 820-01682-08手机点位图
  8. Air720UGUH 极简封装 LTE Cat.1 bis 模块[合宙通信]
  9. element ui Descriptions 组件无法显示样式 未渲染
  10. 2020年中国保理行业市场现状分析,独立化、创新化和多产业渠道是发展关键「图」
  11. 杭州证历本如何使用_药店也可以用
  12. IIO子系统(Linux驱动开发篇)
  13. 51个SIG组,持续12小时在线讨论…openEuler 开源社区这群人为何如此「活力无限」...
  14. 基于visual c++之windows核心编程代码分析(24)IO控制、内核通信
  15. oracle中的replace into
  16. 时间格式中,hh小写的是12小时制,大写(HH)是24小时制的。
  17. Android应用常用的加密方式
  18. [情感]爱的方式(作者:睌风)
  19. ECCV22 | 从单目RGB图像中进行类别级6D物体姿态估计的物体级深度重构
  20. 移动如何使用积分兑换话费

热门文章

  1. 行业指数动量策略+akshare
  2. 【Python】如何判断一个字符串为空
  3. ubuntu java classpath 设置_在Ubuntu中正确设置java classpath和java_home
  4. java get方法不序列化_Java中的Json序列化,不容忽视的getter
  5. c语言贪吃蛇最简单代码_C语言指针,这可能是史上最干最全的讲解啦(附代码)!!!...
  6. php执行npm命令_npm系列之命令执行
  7. 企业网站 源码 服务邮箱:_公司企业邮箱购买,外贸企业邮箱用哪家服务好?
  8. CentOS 安装 php
  9. php汽车找车位,遭遇到车多车位少 教你如何快速找到停车位
  10. mysql 日志_MySQL日志系统