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 和类型转换,这使得代码难以维护和升级,此时,访问者模式的作用就体现出来了。

  • 相关阅读:
    iReaper
    展望未来,总结过去10年的程序员生涯,给程序员小弟弟小妹妹们的一些总结性忠告(turn)
    用C#写ExtJS代码的开源工具extsharp
    如何你是公司的HR,去招聘asp.net程序员,你会对前来面试的人问什么问题。
    ExtJS 3.0 Designer Preview (官方的IDE可视化工具)
    Asp.net ajax、Anthem.net、Ajax pro三大ajax框架那一种使用比较方便?易于配置?
    C#和ASP.net程序员招聘技能要求
    序列化上面创建的Person对象,使其成为一个JSON字符串
    10大加速Ajax开发的框架
    android 解决wifi断线不稳定的问题终极办法
  • 原文地址:https://www.cnblogs.com/start1225/p/6747858.html
Copyright © 2011-2022 走看看