• 【设计模式】 访问者模式


    1、定义

    1.1 标准定义

      访问者模式(Visitor Pattern)是一个相对简单的模式,其定义如下:Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.(封装一些作用于某种数据结构中的各元素的操作, 它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。)

    1.2 类图

      ● Visitor——抽象访问者
      抽象类或者接口, 声明访问者可以访问哪些元素, 具体到程序中就是visit方法的参数定义哪些对象是可以被访问的。

      ● ConcreteVisitor——具体访问者
      它影响访问者访问到一个类后该怎么干, 要做什么事情。

      ● Element——抽象元素
      接口或者抽象类, 声明接受哪一类访问者访问, 程序上是通过accept方法中的参数来定义的。

      ● ConcreteElement——具体元素
      实现accept方法, 通常是visitor.visit(this), 基本上都形成了一种模式了。

      ● ObjectStruture——结构对象
      元素产生者, 一般容纳在多个不同类、 不同接口的容器, 如ListSetMap等, 在项目中, 一般很少抽象出这个角色。

    2、实现

    2.1 类图

      抽象访问者(Visitor)角色:声明了一个或者多个访问操作,形成所有的具体元素角色必须实现的接口。

      具体访问者(ConcreteVisitor)角色:实现抽象访问者角色所声明的接口,也就是抽象访问者所声明的各个访问操作。

      抽象节点(Element)角色:声明一个接受操作,接受一个访问者对象作为一个参量。

      具体节点(ConcreteElement)角色:实现了抽象元素所规定的接受操作。

      结构对象(ObiectStructure)角色:有如下的一些责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如列(List)或集合(Set)。 

    2.2 代码

    #include <iostream>  
    #include <string>  
    #include <list>  
    using namespace std;  
      
    class Element;  
      
    class Visitor  
    {  
    public:  
        virtual void Visit( Element *element ){};  
    };  
      
    // "Element"  
    class Element  
    {  
    public:  
        // Methods  
        virtual void Accept( Visitor *visitor ){};  
    };  
      
      
    // "ConcreteElement"  
    class Employee : public Element  
    {  
    public:  
        string name;  
        double income;  
        int vacationDays;  
      
    public :  
        Employee( string name, double income,  
            int vacationDays )  
        {  
            this->name = name;  
            this->income = income;  
            this->vacationDays = vacationDays;  
        }  
      
        void Accept( Visitor *visitor )  
        {  
            visitor->Visit( this );  
        }  
    };  
      
    class IncomeVisitor : public Visitor  
    {  
    public:   
        void Visit( Element *element )  
        {  
            Employee *employee = ((Employee*)element);  
            employee->income *= 1.10;  
            cout<<employee->name<<" 's new income: " <<employee->income<<endl;  
        }  
    };  
      
    class VacationVisitor : public Visitor  
    {  
    public :  
        void Visit( Element *element )  
        {  
            Employee *employee = ((Employee*)element);  
            // Provide 3 extra vacation days  
            employee->vacationDays += 3;       
            cout<<employee->name<<" 's new vacation days: " <<employee->income<<endl;  
        }  
    };  
      
    // "ObjectStructure"  
    class Employees  
    {     
    private :  
        list< Employee*> employees;  
      
    public :  
      
        void Attach( Employee *employee )  
        {         
            employees.push_back(employee);        
        }  
      
        void Detach( Employee *employee )  
        {  
            employees.remove(employee);       
        }  
      
        void Accept( Visitor *visitor )  
        {         
            for (std::list<Employee*>::iterator it=employees.begin(); it != employees.end(); ++it)  
                (*it)->Accept(visitor);  
        }  
    };  
      
    void main( )  
    {  
        Employees *e = new Employees();  
        e->Attach( new Employee( "Tom", 25000.0, 14 ) );  
        e->Attach( new Employee( "Thomas", 35000.0, 16 ) );  
        e->Attach( new Employee( "Roy", 45000.0, 21 ) );  
      
        // Create two visitors  
        IncomeVisitor *v1 = new IncomeVisitor();  
        VacationVisitor *v2 = new VacationVisitor();  
      
        // Employees are visited  
        e->Accept( v1 );  
        e->Accept( v2 );  
    }  

    3、总结

    3.1 优点

      ● 符合单一职责原则
      具体元素角色也就是Employee抽象类的两个子类负责数据的加载, 而Visitor类则负责报表的展现, 两个不同的职责非常明确地分离开来, 各自演绎变化。

      ● 优秀的扩展性
      由于职责分开, 继续增加对数据的操作是非常快捷的

      ● 灵活性非常高
      如果有
    EmployeeA、EmployeeB、EmployeeC三个甚至更多的元素需要处理,每个元素处理不同,如果你用迭代器加类型判断,OK!可以完成功能,但可能效率很低,多个判断显得代码很乱,如果你用访问者模式,重载某个函数,参数分别传入EmployeeA、EmployeeB、EmployeeC三个类,是不是可以一步解决呢,这就是访问者模式的优势所在,所以,在某些处理方式上面,访问者模式的灵活性相当高。

    3.2 缺点

      ● 具体元素对访问者公布细节
      访问者要访问一个类就必然要求这个类公布一些方法和数据, 也就是说访问者关注了其他类的内部细节, 这是迪米特法则所不建议的。

      ● 具体元素变更比较困难
      具体元素角色的增加、 删除、 修改都是比较困难的, 就上面那个例子, 你想想, 你要是想增加一个成员变量, 如年龄ageVisitor就需要修改, 如果Visitor是一个还好办, 多个呢?业务逻辑再复杂点呢?

      ● 违背了依赖倒置转原则
      访问者依赖的是具体元素, 而不是抽象元素, 这破坏了依赖倒置原则, 特别是在面向对象的编程中, 抛弃了对接口的依赖, 而直接依赖实现类, 扩展比较难。

    3.3 使用场景

      ● 一个对象结构包含很多类对象, 它们有不同的接口, 而你想对这些对象实施一些依赖于其具体类的操作, 也就说是用迭代器模式已经不能胜任的情景。

      ● 需要对一个对象结构中的对象进行很多不同并且不相关的操作, 而你想避免让这些操作污染这些对象的类。

      总结一下, 在这种地方你一定要考虑使用访问者模式: 业务规则要求遍历多个不同的对象。 这本身也是访问者模式出发点, 迭代器模式只能访问同类或同接口的数据( 当然了, 如果你使用instanceof, 那么能访问所有的数据, 这没有争论) , 而访问者模式是对迭代器模式的扩充, 可以遍历不同的对象, 然后执行不同的操作, 也就是针对访问的对象不同, 执行不同的操作。 访问者模式还有一个用途, 就是充当拦截器( Interceptor) 角色。

  • 相关阅读:
    老男孩Day17作业:后台管理平台编辑表格
    老男孩Day16作业:登录、注册、后台管理页面(动态)
    老男孩Day15作业:商城列表页面(静态)
    老男孩Day14作业:堡垒机
    老男孩Day13作业:ORM学员管理系统
    老男孩Day12作业:RabbitMQ-RPC版主机管理程序
    老男孩Day10作业:主机管理程序
    老男孩Day9作业:高级FTP
    面试遇见钓鱼公司怎么办?
    宝能技术岗面试
  • 原文地址:https://www.cnblogs.com/ChinaHook/p/7302499.html
Copyright © 2020-2023  润新知