访问者模式(Visitor)及代码实现

模式定义:

在不修改现有类的代码的情况下,通过 将类的对象 赋值给其他 类进行操作,从而增加新的功能的方式为 访问者模式;

 

生活中的例子

  • 去服饰店买衣服时,客户对不同类型的衣服 的价格持不同意见; 店员对不同类型的衣服 推销时的话术也不同;
  • 举个贴切的例子吧,正好项目要上线;  项目上到预发布环境,产品 检查项目是否满足需求,测试 检查项目是否有bug,开发 等着项目出bug......

 

何时该使用此模式:

  • 在不想改变现有类的结构代码,又想增加新的功能,可以使用此模式 以增加最少代码为代价增加新功能;
  • 一系列相似对象 需要提供 多种不同且不相关的功能时,且 尽量减少现有类的变化导致的风险时,可以使用此模式;

访问者模式(Visitor)及代码实现

实现方式:

访问者模式(Visitor)及代码实现

 

该模式关键的角色:

  • 抽象访问者角色(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)

运行结果:

访问者模式(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

 

上一篇:


下一篇:JavaScript 高级五 let; const;解析赋值;箭头函数;剩余参数;Array的扩展方法;String的扩展方法;set 数据结构