访问者模式(Visitor)及代码实现
模式定义:
在不修改现有类的代码的情况下,通过 将类的对象 赋值给其他 类进行操作,从而增加新的功能的方式为 访问者模式;
生活中的例子
- 去服饰店买衣服时,客户对不同类型的衣服 的价格持不同意见; 店员对不同类型的衣服 推销时的话术也不同;
- 举个贴切的例子吧,正好项目要上线; 项目上到预发布环境,产品 检查项目是否满足需求,测试 检查项目是否有bug,开发 等着项目出bug......
何时该使用此模式:
- 在不想改变现有类的结构代码,又想增加新的功能,可以使用此模式 以增加最少代码为代价增加新功能;
- 一系列相似对象 需要提供 多种不同且不相关的功能时,且 尽量减少现有类的变化导致的风险时,可以使用此模式;
实现方式:
该模式关键的角色:
- 抽象访问者角色(Abs Visitor): 定义一个公共的访问具体元素的抽象方法,参数为 具体的元素对象
- 具体访问者角色(Concrete Visitor):实现 访问具体元素的 方法;注意 需要实现 双分派功能(你的对象里的变量有我这个对象,我的对象里的变量有你这个对象);
- 抽象元素角色(Abs Element): 定义一个 供 访问者 访问的抽象方法; 参数为 访问者对象;
- 具体元素角色(Concrete Element): 实现 供访问者 访问的方法;注意 需要实现 双分派功能(你中有我,我中有你);
该模式的主要优缺点如下:
优点:
- 部分符合 开闭原则: 在只增加 访问者 功能时,只需按照规则 增加新的访问者即可,完全满足开闭原则;但是 在 增加元素时,不仅增加了 新的元素类,访问者类中 同样增加;
- 符合单一职责原则: 此模式将 对数据处理的功能 解耦在每个访问者中,达到 每个访问者功能单一的目的;
- 将 数据结构 和 数据处理进行解耦; 数据处理在 访问者类中,数据结构在 元素类中; 这样在有新的数据处理功能时 能快速扩展;
缺点:
- 只要 元素 类有所修改,对应的 所有访问者 类都要修改; 不符合开闭原则;
- 违反依赖倒置原则; 访问者类 依赖具体的 元素类来进行对应操作;而不是依赖抽象类;
- 破坏封装; 具体元素对 访问者暴露无遗, 没有提供封装(也正是因为这种特点 才能让访问者完全操作元素);
- 使代码 更加复杂,理解起来更加困难,特别是 双分派功能的使用
和 其他模式 的 比较:
与 原型模式 比较: 两者没啥相关性,但是 在不想修改元素对象时,可以结合使用;
示例代码部分:
# -*- coding: utf-8 -*-
"""
(C) rgc
All rights reserved
create time '2020/12/29 19:31'
Usage:
访问者模式示例
购买各种礼物时 顾客和店主 对礼物的处理不同;顾客考虑礼物价格是否划算,大小是否合适,店主考虑礼物 根据价格和大小 应该怎么包装才合适
这个需求中 各种礼物是 元素;顾客和店主 是访问者;
假如现在有2种礼物,则顾客和店主需要 共需要实现 2*2=4种访问方法
现在增加到了5种礼物,则顾客和店主需要 共需要实现 2*5=10种方法方法
因为顾客和店主 对每种礼物 都要分别进行相应操作;
"""
from abc import ABC, abstractmethod
class Gift(ABC):
"""
礼物基类
抽象元素角色
"""
def __init__(self, price, height):
self.price = price
self.height = height
@abstractmethod
def accept(self, visitor):
"""
:param visitor:
:return:
"""
pass
class Lighter(Gift):
"""
打火机类
具体元素角色
"""
def accept(self, visitor):
"""
:param visitor:
:return:
"""
return visitor.visit_lighter(self)
class Toys(Gift):
"""
玩具类
具体元素角色
"""
def accept(self, visitor):
"""
:param visitor:
:return:
"""
return visitor.visit_toys(self)
class Cup(Gift):
"""
杯子类
具体元素角色
"""
def accept(self, visitor):
"""
:param visitor:
:return:
"""
return visitor.visit_cup(self)
class Person(ABC):
"""
人 类
抽象访问者角色
"""
@abstractmethod
def visit_lighter(self, element: Lighter):
"""
对 打火机礼物进行的操作
:param element:
:return:
"""
pass
@abstractmethod
def visit_toys(self, element: Toys):
"""
对 玩具礼物进行的操作
:param element:
:return:
"""
pass
@abstractmethod
def visit_cup(self, element: Cup):
"""
对 杯子 礼物进行的操作
:param element:
:return:
"""
pass
class Customer(Person):
"""
顾客 类
具体访问者角色
"""
def visit_lighter(self, element: Lighter):
"""
对 打火机礼物进行的操作
:param element:
:return:
"""
if element.height < 7:
print('打火机大小还行')
else:
print('打火机太大了,不方便')
if element.price > 2000:
print('什么破打火机怎么这么贵')
else:
print('嗯...,这个打火机还行')
return True
return False
def visit_toys(self, element: Toys):
"""
对 玩具礼物进行的操作
:param element:
:return:
"""
print('玩具不在乎高度问题')
if element.price > 2000:
print('天啊,这个玩具好贵啊')
return False
print('小意思,这个玩具可以考虑')
return True
def visit_cup(self, element: Cup):
"""
对 杯子 礼物进行的操作
:param element:
:return:
"""
print('杯子不考虑高度问题')
if 100 < element.price < 300:
print('这个杯子可以,不贵也不便宜')
return True
print('不考虑这个杯子')
return False
class Clerk(Person):
"""
店员 类
具体访问者角色
"""
def visit_lighter(self, element: Lighter):
"""
对 打火机礼物进行的操作
:param element:
:return:
"""
if element.height > 6:
print('这个打火机要用长点的盒子包装')
else:
print('这个打火机用小盒子包装就行了')
def visit_toys(self, element: Toys):
"""
对 玩具礼物进行的操作
:param element:
:return:
"""
if element.price > 1000:
print('这个玩具挺贵的,用高档材料包装吧')
else:
print('这个玩具价格一般,随便包装一下吧')
def visit_cup(self, element: Cup):
"""
对 杯子 礼物进行的操作
:param element:
:return:
"""
if element.height > 10:
print('这个杯子有点大,用箱子包装吧')
else:
print('挺小一杯子,用小盒子吧,别浪费资源')
if __name__ == '__main__':
# 各种礼物初始化
gifts = [Lighter(3000, 6), Toys(2000, 50), Cup(200, 10)]
print('客户挑选礼物')
customer_visitor = Customer()
# 礼物列表
choose_gift_list = []
# 查看每个礼物
for gift in gifts:
# 把心仪的礼物放到礼物列表中
if gift.accept(customer_visitor):
choose_gift_list.append(gift)
print('*' * 10)
print(f'店员看到到顾客选择了{len(choose_gift_list)}个礼物,现在开始包装')
clerk_visitor = Clerk()
# 店员对顾客选择的礼物进行包装
for gift in choose_gift_list:
gift.accept(clerk_visitor)
运行结果:
总结:
注意 在访问者类 中操作 元素对象时,除非需要,否则尽量不要修改现有对象属性值(可以通过 原型模式进行克隆),防止 影响后续操作;
这种模式 打破了 程序员编写代码时的思想禁锢, 一般都是 数据对象增加新功能时 都是在类中直接增加新的方法; 而此模式 将 新功能放在单独的类中;
相关链接:
https://refactoringguru.cn/design-patterns/visitor
https://refactoringguru.cn/design-patterns/visitor/python/example
http://c.biancheng.net/view/1397.html
https://www.cnblogs.com/liuqingzheng/articles/10039702.html
北风之神c: 总结的很全面,写得赞,博主用心了。 celery对目录层级文件名称格式要求太高,只适合规划新的项目,对不规则文件夹套用难度高。 所以新手使用celery很仔细的建立文件夹名字、文件夹层级、python文件名字。 所以网上的celery博客教程虽然很多,但是并不能学会使用,因为要运行起来需要以下6个方面都掌握好,博客文字很难表达清楚或者没有写全面以下6个方面。 celery消费任务不执行或者报错NotRegistered,与很多方面有关系,如果要别人排错,至少要发以下6方面的截图,因为与一下6点关系很大。 1)整个项目目录结构, 2)@task入参 ,3)celery的配置,4)celery的配置 include ,5)cmd命令行启动参数 --queues= 的值,6)用户在启动cmd命令行时候,用户所在的文件夹。 在不规范的文件夹路径下,使用celery难度很高,一般教程都没教。 [项目文件夹目录格式不规范下的celery使用演示](https://github.com/ydf0509/celery_demo) 。 此国产分布式函数调度框架 funboost python万能通用函数加速器 https://funboost.readthedocs.io/zh-cn/latest/articles/c1.html , 从用法调用难度,用户所需代码量,超高并发性能,qps控频精确程度,支持的中间件类型,任务控制方式,稳定程度等20个方面全方位超过celery。发布性能提高1000%,消费性能提高2000%。 python万能分布式函数调度框架funboost支持python所有类型的并发模式和一切知名消息队列中间件,python函数加速器,只需要一行代码调度任意函数,框架包罗万象,万能编程功能宝典,一统编程思维,与业务不绑定,适用范围广。 pip install funboost
qq_58685443: 没有嘞,最好还是用rstp推流或者是http吧
m0_52854542: 我也是用mqtt协议发送图片,帧率低,你之后有好的改进方案吗
Rrriobun: 为什么会出现问题2这种问题啊?(问题2: 在使用loky进行多进程并发时,无法 实现 先执行完的任务先返回给 用户端 的功能,让我的接口效应时间大打折扣;)
qq_58685443: 博主用这个方案帧率怎么样啊,我没加其他功能,直接解析通过mqtt协议发来图片帧率好低