zoukankan      html  css  js  c++  java
  • 10 行为型模式之

    访问者模式介绍:访问者模式是一种将数据操作与数据结构分离的设计模式,它是《设计模式》中23种设计模式中最复杂的一个,但是它的使用频率并不高,正如《设计模式》的作者GOF对访问者模式的描述:大多数情况下,你并不需要使用访问者模式,但是当你一旦需要使用它时,那你就是真地需要它了。

      访问者模式的基本想法是,软件系统中拥有一个由许多对象构成的,比较稳定的对象结构,这些对象的类都拥有一个accept方法,用来接受访问者对象的访问。访问者是一个接口,它拥有一个visit方法,从而使访问者得以处理对象结构的每一个元素,我们可以针对对象结构设计不同的访问者类来完成不同的操作,达到区别对待的效果

    访问者模式的定义:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义 作用于这些元素的新的操作。

    访问者模式使用的场景:

    1 对象结构比较稳定,但是经常需要在此对象结构上定义新的操作

    2 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类

    简单示例:

    在年终时,公司都会给员工进行业绩考核,以此来评定该员工的绩效及年终奖,晋升等,这些评定都是由公司高层来负责,但是,不同领域的管理人员对员工的评定是不一样的,为了简单明了地说明问题,我们简单地把员工分为工程师和经理,评定员工的分别为CEO和CTO,我们假定CTO只关注工程师的代码量,经理的产品数量。而CEO关注的是工程师的KPI和经理的KPI以及产品的数量,从中可以看出,CEO和CTO对于不同员工的关注点是不一样的,这就需要对不同的员工类型进行不同的处理,访问者模式此时可以派上用场了,我们看看下面相关代码的实现

    员工基类:

     1 /**
     2  * 员工基类
     3  */
     4 public abstract class Staff {
     5     public String name;
     6 
     7     //员工KPI
     8     public int kpi;
     9 
    10     public Staff(String name){
    11         this.name = name;
    12         kpi = new Random().nextInt();
    13     }
    14 
    15     // 接受visitor的访问
    16     public abstract void accept(Visitor visitor);
    17 }

    Staff类定义了员工的基本信息及一个accept方法,accept方法表示接受访问者的访问,由子类具体实现。下面看看工程师和经理的代码:

     1 /**
     2  * 工程师类型
     3  */
     4 public class Engineer extends Staff {
     5     public Engineer(String name){
     6         super(name);
     7     }
     8 
     9     @Override
    10     public void accept(Visitor visitor) {
    11         visitor.visit(this);
    12     }
    13 
    14     //工程师这一年写的代码数量
    15     public int getCodeLine(){
    16         return new Random().nextInt() * 10000;
    17     }
    18 }
    /**
     * 经理类型
     */
    public class Manager extends Staff{
        public Manager(String name){
            super(name);
        }
    
        @Override
        public void accept(Visitor visitor) {
            visitor.visit(this);
        }
    
        //经理一年内做的产品的数量
        public int getProducts(){
            return 10;
        }
    }

    在工程师类中添加了获取代码行数的函数,而在经理类中则添加了获取新产品数量的函数,它们的职责是不一样的,也正是由于它们的差异性才使得访问者模式能够发挥它的作用。 Staff , Engineer , Manager 3个类型就是对象结构,这些类型相对稳定,不会发生变化。

      然后将这些员工添加到一个业务报表类中,公司高层可以通过该报表类的showReport函数查看所有员工的业绩。具体代码如下:

     1 /**
     2  * 员工业务报表
     3  */
     4 public class BusinessReport {
     5     List<Staff> mStaffs = new ArrayList<>();
     6 
     7     public BusinessReport(){
     8         mStaffs.add(new Manager("王经理"));
     9         mStaffs.add(new Engineer("工程师-小A"));
    10         mStaffs.add(new Engineer("工程师-小B"));
    11         mStaffs.add(new Engineer("工程师-小C"));
    12     }
    13 
    14     /**
    15      * 为访问者展示报表,公司高层,如CEO,CTO
    16      */
    17     public void showReport(Visitor visitor){
    18         for (Staff staff : mStaffs){
    19             staff.accept(visitor);
    20         }
    21     }
    22 }

    下面看看访问者 Visitor类型的定义,Visitor声明了两个visit函数,分别是对工程师和经理的访问函数,具体代码如下:

    /**
     * 访问者
     */
    public interface Visitor {
        //访问工程师类型
        void visit(Engineer engineer);
    
        //访问经理类型
        void visit(Manager manager);
    }

    Visitor接口,该接口有两个visit函数,参数分别为Engineer,Manager,也就是说对于Engineer,Manager的访问会调用两个不同的方法,以此达成区别对待,差异化处理。具体的实现类为CEOVisitor, CTOVisitor,具体代码如下:

    /**
     * CEO访问类型,只关注业绩
     */
    public class CEOVisitor implements Visitor{
        @Override
        public void visit(Engineer engineer) {
            System.out.println("工程师:"+engineer.name + " KPI : " + engineer.kpi);
        }
    
        @Override
        public void visit(Manager manager) {
            System.out.println("经理:"+manager.name + " KPI : " + manager.kpi + " , 新产品数量:" + manager.getProducts());
        }
    }
    /**
     * CTO 类型,只关注技术层面上的贡献
     */
    public class CTOVisitor implements Visitor{
        @Override
        public void visit(Engineer engineer) {
            System.out.println("工程师 : "+engineer.name + " , 代码行数:" + engineer.getCodeLine());
        }
    
        @Override
        public void visit(Manager manager) {
            System.out.println("经理 : "+manager.name + " , 产品数量:" + manager.getProducts());
        }
    }

    在CEO的访问者中,CEO只关注Engineer员工的KPI,而对于Manager类型的员工除了KPI之外还有该Manager本年度新开发产品的数量,两类员工的关注点不同,通过两个visitor方法分别进行处理。而如果不使用访问者模式,只是通过一个visitor函数进行处理,那么就需要在这个visit函数中对不同的员工类进行判断,然后分别处理,代码大致如下:

     1 /**
     2  * 不使用访问者模式的写法
     3  */
     4 public class ReportUtils {
     5     public void visit(Staff staff){
     6         if(staff instanceof Engineer){
     7             Engineer engineer = (Engineer) staff;
     8             System.out.println("工程师:" + engineer.name + " , KPI = "+ engineer.kpi);
     9         }else if(staff instanceof Manager){
    10             Manager manager = (Manager) staff;
    11             System.out.println("经理:"+manager.name + " , KPI : "+manager.kpi + ",新产品数量 : "+ manager.getProducts());
    12         }
    13     }
    14 }

    这就导致了if else 逻辑的嵌套以及类型的强制转换,难以维护和扩展,当类型较多时,这个ReportUtils就会很复杂,而使用Visitor模式,通过同一个函数对不同的元素类型进行相应处理,使得结构更加清晰,灵活性更高

    下面是使用访问者模式的客户端的用法,代码如下:

     1 /**
     2  *  客户端
     3  */
     4 public class Client {
     5     public static void main(String[] args){
     6         //构建报表
     7         BusinessReport businessReport = new BusinessReport();
     8         System.out.println("========== 给CEO看的报表 ============");
     9         businessReport.showReport(new CEOVisitor());
    10         System.out.println("========== 给CTO看的报表 ============");
    11         businessReport.showReport(new CTOVisitor());
    12     }
    13 }

    客户端代码中,首先构建了一个报表对象,该对象中维护了所有员工的集合,然后通过报表类的showReport函数为Visitor对象提供一个访问接口,在这个函数中遍历所有的员工,然后调用员工的accept函数接受访问者的访问,每个访问者对不同类型的员工调用对应的visit函数实现不同的操作

    具体输出如下:

    ========== 给CEO看的报表 ============
    经理:王经理 KPI : -521144346 , 新产品数量:10
    工程师:工程师-小A KPI : -1619137302
    工程师:工程师-小B KPI : -237723895
    工程师:工程师-小C KPI : 1468883099
    ========== 给CTO看的报表 ============
    经理 : 王经理 , 产品数量:10
    工程师 : 工程师-小A , 代码行数:646642576
    工程师 : 工程师-小B , 代码行数:-1775007488
    工程师 : 工程师-小C , 代码行数:-1605653216

    访问者模式最大的优点就是增加访问者非常容易。我们从代码中可以看到,如果要增加一个访问者,你新创建一个实现了Visitor接口的类,然后实现两个visitor函数来对不同的元素进行不同的操作,从而达到数据对象与数据操作相分离的效果,如果不使用访问者模式,而又想对不同的元素进行不同的操作,那么必定需要使用 if else 和类型转换,这使得代码难以维护和升级,此时,访问者模式的作用就体现出来了。

  • 相关阅读:
    zoj 1239 Hanoi Tower Troubles Again!
    zoj 1221 Risk
    uva 10192 Vacation
    uva 10066 The Twin Towers
    uva 531 Compromise
    uva 103 Stacking Boxes
    稳定婚姻模型
    Ants UVA
    Golden Tiger Claw UVA
    关于upper、lower bound 的探讨
  • 原文地址:https://www.cnblogs.com/start1225/p/6747858.html
Copyright © 2011-2022 走看看