模式定义:
在不修改现有类的代码的情况下,通过 将类的对象 赋值给其他 类进行操作,从而增加新的功能的方式为 访问者模式;
生活中的例子
- 去服饰店买衣服时,客户对不同类型的衣服 的价格持不同意见; 店员对不同类型的衣服 推销时的话术也不同;
- 举个贴切的例子吧,正好项目要上线; 项目上到预发布环境,产品 检查项目是否满足需求,测试 检查项目是否有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