访问者模式
是一种将算法和其作用的对象分离开的设计模式。 这种模式允许在不改变现有对象结构情况下向对象结构中添加新的行为
实现思路
核心思想是: 将一系列算法封装到访问者对象中,并将访问者对象传递给对象结构中的元素,以便元素可以调用访问者对象中的算法。
精髓:访问者对象可以通过访问元素中的数据和操作来实现算法,从而避免了对元素结构的直接访问
访问者模式的角色
- 访问者接口: 定义了访问元素的方法,这些方法通常以不同的重载形式出现,以便针对不同类型的元素采取不同的行为
- 具体访问者:实现访问者接口,提供了算法的具体实现
- 元素:定义了用于接收访问者的方法,通常以accept()的形式出现, 元素可以将自己作为参数传递给访问者对象
- 具体元素:实现了元素接口,提供了具体的数据和操作,同时提供了接收访问者的方法
- 对象结构(可不需要):定义了元素的集合,可以提供一些方法以便访问者能够遍历整个集合。
访问者模式的优缺点
优点:
- 单一职责原则: 每个类只负责一项职责
- 良好的封装性:将算法与其作用的对象分离开来,避免了对元素结构的直接访问
- 良好的扩展性:可以方便的实现元素结构的复杂算法,不需要修改元素结构本身
缺点:
- 违反迪米特原则:元素向访问者暴露了内部信息。
- 实现较为复杂,可能会导致访问者对象的复杂性,不易理解
- 元素类难以变更: 元素类需要维持与访问者的兼容,可能会导致元素结构的扩展性变得差,因为每添加一个新的元素类型时,都需要修改所有的访问者对象
- 依赖具体类: 访问者模式依赖于具体类而不是接口, 违反了依赖倒置原则。
应用场景
报表生成、用户界面显示、拦截器和过滤器等。
"""
图形编辑器
"""
from abc import ABC, abstractmethod
# 抽象图形元素类 包含绘制方法和接受访问者的方法
class GraphicElement(ABC):
@abstractmethod
def draw(self):
"""绘制图形元素"""
pass
@abstractmethod
def accept(self, visitor):
"""接受访问者"""
pass
# 具体图形元素类:圆形、矩形、三角形
class Circle(GraphicElement):
def draw(self):
print("绘制圆形。")
def accept(self, visitor):
visitor.visit_circle(self)
class Rectangle(GraphicElement):
def draw(self):
print("绘制矩形。")
def accept(self, visitor):
visitor.visit_rectangle(self)
class Triangle(GraphicElement):
def draw(self):
print("绘制三角形。")
def accept(self, visitor):
visitor.visit_triangle(self)
# 定义访问者接口,包含对不同图形元素的操作方法
class GraphicVisitor(ABC):
@abstractmethod
def visit_circle(self, circle):
"""访问圆形"""
pass
@abstractmethod
def visit_rectangle(self, rectangle):
"""访问矩形"""
pass
@abstractmethod
def visit_triangle(self, triangle):
"""访问三角形"""
pass
# 具体访问者类:选择工具、填充工具
class SelectToolVisitor(GraphicVisitor):
def visit_circle(self, circle):
print("选择圆形。")
def visit_rectangle(self, rectangle):
print("选择矩形。")
def visit_triangle(self, triangle):
print("选择三角形。")
class FillToolVisitor(GraphicVisitor):
def visit_circle(self, circle):
print("填充圆形。")
def visit_rectangle(self, rectangle):
print("填充矩形。")
def visit_triangle(self, triangle):
print("填充三角形。")
# 对象结构类
class Structure:
def __init__(self):
self.elements = []
self.visitors = []
def add_elements(self, element):
self.elements.append(element)
def add_visitors(self, visitor):
self.visitors.append(visitor)
def accept(self):
for visitor in self.visitors:
for element in self.elements:
element.accept(visitor)
def main():
circle = Circle()
rectangle = Rectangle()
triangle = Triangle()
select_tool = SelectToolVisitor()
fill_tool = FillToolVisitor()
print("使用选择工具:")
circle.accept(select_tool)
rectangle.accept(select_tool)
triangle.accept(select_tool)
print("\n使用填充工具:")
circle.accept(fill_tool)
rectangle.accept(fill_tool)
triangle.accept(fill_tool)
# 通过对象结构类调用
print("\n通过对象结构类调用:")
structure = Structure()
structure.add_elements(circle)
structure.add_elements(rectangle)
structure.add_elements(triangle)
structure.add_visitors(select_tool)
structure.add_visitors(fill_tool)
structure.accept()
if __name__ == "__main__":
main()
虚构故事
接-模板方法模式中的比特咖啡故事(传送门), 老板姬比特打算把拉花引入到比特咖啡中(其本质是通过一系列预设的形状来“渲染”咖啡),比特咖啡APP的普通会员只能在配置界面选择基础的拉花图案,而VIP会员可以选择更加精美和丰富的拉花图案(而且保留VIP用户可以自定义拉花图案的功能),也就是不同角色的访客看到的拉花配置和视觉效果都不同,老板给这个功能起了一个非常”drama“的名字作为宣传点——“艺术咖系列”,把使用这个APP的用户称为”涂鸦艺术家“。
这个任务又交给了我们的程序员-幸瑞。
幸瑞打算在之前一节 模板方法模式中那样,在 AmericanoMaker 和 CappuccinoMaker 的父类 CoffeeMaker 中添加一个 制作拉花的方法,但是想了下觉得不太合适,因为美式咖啡是做不了拉花的。 但是如果只在 CappuccinoMaker 后增加制作拉花的方法那如果之后又要加入 拿铁、玛奇朵、摩卡等等,就需要在每一个类后面都追加方法,这不太现实。 他想到了使用 组合的方式来重构制作流程,但是又考虑到比特咖啡APP本质是一个番茄钟(用制作咖啡的“咖啡时间”来模拟番茄钟的时间周期),而且人们常饮用的咖啡也就那几种,像——比特咖啡这种虚拟的数字咖啡,重点在UI、UE上。 用户看到的是一种制作的过程和效果。 可以做一个配置页面用于用户的自定义配置,制作咖啡的时候先读取配置然后根据用户的自定义配置微调。 想到这… 于是幸瑞敲击着键盘记录下了他的想法(demo)。
他稍微改了下之前-模板方法一节中的 CoffeeMaker 咖啡制作基类,修改如下:
class CoffeeMaker(ABC):
def make_coffee(self):
"""模板方法,定义制作咖啡的整体流程"""
self.read_config() # 新增读取配置
self.grind_coffee()
self.brew_coffee()
self.add_ingredients()
self.finish_coffee()
def read_config(self):
"""读取配置"""
return "配置信息"
@abstractmethod
def grind_coffee(self):
"""抽象方法:研磨咖啡豆"""
pass
@abstractmethod
def brew_coffee(self):
"""抽象方法:冲泡咖啡"""
pass
def add_ingredients(self):
"""默认方法:添加配料"""
# 默认不添加任何配料,子类可以重写
pass
@abstractmethod
def finish_coffee(self):
"""抽象方法:完成咖啡制作"""
pass
class CoffeeArtConfigPage(ABC):
@abstractmethod
def select(self):
""" 选择配置"""
pass
def read(self):
""" 提供对外的读取配置的接口"""
return "配置信息"
@abstractmethod
def accept_visitor(self, visitor):
pass
class BasicCoffeeArtConfigPage(CoffeeArtConfigPage):
def select(self):
print("基础的拉花图案配置库")
def accept_visitor(self, visitor):
visitor.visit_config(self)
class VIPCoffeeArtConfigPage(CoffeeArtConfigPage):
def select(self):
print("精美的拉花图案配置库")
def accept_visitor(self, visitor):
visitor.visit_config(self)
class UserVisitor(ABC):
@abstractmethod
def visit_config(self, config_page):
pass
class GeneralUserVisitor(UserVisitor):
def visit_config(self, config_page):
print("普通用户可以选择基础的拉花图案。")
config_page.select()
class VipUserVisitor(UserVisitor):
def visit_config(self, config_page):
print("VIP用户可以选择精美的拉花图案。")
config_page.select()
demo算是写的差不多了,这还只是开始,后面还有很多需要完善… 快中午了,幸瑞准备起身去干饭了 等会再写…