美文网首页
访问者模式

访问者模式

作者: 竖起大拇指 | 来源:发表于2020-11-27 11:04 被阅读0次

    使用场景

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

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

    访问者模式的UML类图

    image.png

    例子(来源于网络,因为比较形象 容易理解 故摘抄记录)

    年底,CEO和CTO开始评定员工一年的工作绩效,员工分为工程师和经理,CTO关注工程师的代码里,经理的新产品数量;CEO关注的是工程师的KPI和经理的KPI以及新产品数量。

    由于CEO和CTO对于不同员工的关注点是不一样的,这就需要对不同员工类型进行不同的处理。访问者模式此时就可以派上用场了。

    // 员工基类
    public abstract class Staff {
    
        public String name;
        public int kpi;// 员工KPI
    
        public Staff(String name) {
            this.name = name;
            kpi = new Random().nextInt(10);
        }
        // 核心方法,接受Visitor的访问
        public abstract void accept(Visitor visitor);
    }
    

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

    // 工程师
    public class Engineer extends Staff {
    
        public Engineer(String name) {
            super(name);
        }
    
        @Override
        public void accept(Visitor visitor) {
            visitor.visit(this);
        }
        // 工程师一年的代码数量
        public int getCodeLines() {
            return new Random().nextInt(10 * 10000);
        }
    }
    
    // 经理
    public class Manager extends Staff {
    
        public Manager(String name) {
            super(name);
        }
    
        @Override
        public void accept(Visitor visitor) {
            visitor.visit(this);
        }
        // 一年做的产品数量
        public int getProducts() {
            return new Random().nextInt(10);
        }
    }
    

    工程师是代码数量,经理是产品数量,他们的职责不一样,也就是因为差异性,才使得访问模式能够发挥它的作用。Staff,Enginner,Manager 3个类型就是对象结构,这些类型相对稳定,不会发生变化。

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

    // 员工业务报表类
    public class BusinessReport {
    
        private List<Staff> mStaffs = new LinkedList<>();
    
        public BusinessReport() {
            mStaffs.add(new Manager("经理-A"));
            mStaffs.add(new Engineer("工程师-A"));
            mStaffs.add(new Engineer("工程师-B"));
            mStaffs.add(new Engineer("工程师-C"));
            mStaffs.add(new Manager("经理-B"));
            mStaffs.add(new Engineer("工程师-D"));
        }
    
        /**
         * 为访问者展示报表
         * @param visitor 公司高层,如CEO、CTO
         */
        public void showReport(Visitor visitor) {
            for (Staff staff : mStaffs) {
                staff.accept(visitor);
            }
        }
    }
    

    下面看看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());
        }
    }
    

    在CEO的访问者中,CEO关注工程师的 KPI,经理的 KPI 和新产品数量,通过两个 visitor 方法分别进行处理。如果不使用 Visitor 模式,只通过一个 visit 方法进行处理,那么就需要在这个 visit 方法中进行判断,然后分别处理,代码大致如下:

    public class ReportUtil {
        public void visit(Staff staff) {
            if (staff instanceof Manager) {
                Manager manager = (Manager) staff;
                System.out.println("经理: " + manager.name + ", KPI: " + manager.kpi +
                        ", 新产品数量: " + manager.getProducts());
            } else if (staff instanceof Engineer) {
                Engineer engineer = (Engineer) staff;
                System.out.println("工程师: " + engineer.name + ", KPI: " + engineer.kpi);
            }
        }
    }
    

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

    在添加一个CTO的Visitor类:

    public class CTOVisitor implements Visitor {
        @Override
        public void visit(Engineer engineer) {
            System.out.println("工程师: " + engineer.name + ", 代码行数: " + engineer.getCodeLines());
        }
    
        @Override
        public void visit(Manager manager) {
            System.out.println("经理: " + manager.name + ", 产品数量: " + manager.getProducts());
        }
    }
    

    重载的visit方法会对元素进行不同的操作,而通过注入不同的Visitor又可以替换掉访问者的具体实现 ,使得对元素的操作变得更灵活,可扩展性更高,同时也消除了类型转换, if-else 等丑陋的代码。
    下面是客户端代码:

    public class Client {
    
        public static void main(String[] args) {
            // 构建报表
            BusinessReport report = new BusinessReport();
            System.out.println("=========== CEO看报表 ===========");
            report.showReport(new CEOVisitor());
            System.out.println("=========== CTO看报表 ===========");
            report.showReport(new CTOVisitor());
        }
    }
    

    在上述示例中,Staff 扮演了 Element 角色,而 Engineer 和 Manager 都是 ConcreteElement;CEOVisitor 和 CTOVisitor 都是具体的 Visitor 对象;而 BusinessReport 就是 ObjectStructure;Client就是客户端代码。
    访问者模式最大的优点就是增加访问者非常容易,我们从代码中可以看到,如果要增加一个访问者,只要新实现一个 Visitor 接口的类,从而达到数据对象与数据操作相分离的效果。如果不实用访问者模式,而又不想对不同的元素进行不同的操作,那么必定需要使用 if-else 和类型转换,这使得代码难以升级维护。

    总结

    我们要根据具体情况来评估是否适合使用访问者模式,例如,我们的对象结构是否足够稳定,是否需要经常定义新的操作,使用访问者模式是否能优化我们的代码,而不是使我们的代码变得更复杂。

    • 访问者模式的优点。

    1.各角色职责分离,符合单一职责原则
    通过UML类图和上面的示例可以看出来,Visitor、ConcreteVisitor、Element 、ObjectStructure,职责单一,各司其责。

    2.具有优秀的扩展性
    如果需要增加新的访问者,增加实现类 ConcreteVisitor 就可以快速扩展。

    3.使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
    员工属性(数据结构)CEO、CTO访问者(数据操作)的解耦。

    4.灵活性

    • 访问者模式的缺点。

    1.具体元素对访问者公布细节,违反了迪米特原则
    CEO、CTO需要调用具体员工的方法。

    2.具体元素变更时导致修改成本大
    变更员工属性时,多个访问者都要修改。

    3.违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有以来抽象
    访问者 visit 方法中,依赖了具体员工的具体方法。

    相关文章

      网友评论

          本文标题:访问者模式

          本文链接:https://www.haomeiwen.com/subject/vzztwktx.html