zoukankan      html  css  js  c++  java
  • 「补课」进行时:设计模式(18)——访问者模式

    1. 前文汇总

    「补课」进行时:设计模式系列

    2. 引言

    访问者模式也可以说是所有设计模式中最难的一种设计模式了,当然我们平常也很少会用到它。设计模式的作者是这么评价访问者模式的:大多情况下,你并不需要使用访问者模式,但是一旦需要使用它时,那就真的需要使用了。

    3. 一个简单的示例

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

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

    首先定义一个员工基类 Staff :

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

    Staff 类定义了员工基本信息及一个 accept() 方法, accept() 方法表示接受访问者的访问,由子类具体实现。

    Visitor 是个接口,传入不同的实现类,可访问不同的数据。

    下面是工程师和经理的具体实现类:

    public class Engineer extends Staff {
        public Engineer(String name) {
            super(name);
        }
        @Override
        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
        void accept(Visitor visitor) {
            visitor.visit(this);
        }
        // 一年做的产品数量
        public int getProducts() {
            return new Random().nextInt(10);
        }
    }
    

    工程师是代码数量,经理是产品数量,他们的职责不一样,也就是因为差异性,才使得访问模式能够发挥它的作用。

    下面是 Visitor 接口的定义:

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

    Visitor 声明了两个 visit 方法,分别是对工程师和经理对访问函数。

    接下来定义两个具体的访问者: CEO 和 CTO 。

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

    接着是一个报表类,公司的 CEO 和 CTO 通过这个报表查看所有员工的业绩:

    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 Manager("经理-B"));
            mStaffs.add(new Engineer("工程师-C"));
        }
        /**
         * 为访问者展示报表
         * @param visitor 公司高层,如 CEO、CTO
         */
        public void showReport(Visitor visitor) {
            for (Staff staff : mStaffs) {
                staff.accept(visitor);
            }
        }
    }
    

    最后是一个场景类:

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

    执行结果如下:

    =========== CEO看报表 ===========
    经理: 经理-A, KPI: 7, 新产品数量: 8
    工程师: 工程师-A, KPI: 6
    工程师: 工程师-B, KPI: 3
    经理: 经理-B, KPI: 4, 新产品数量: 4
    工程师: 工程师-C, KPI: 2
    =========== CTO看报表 ===========
    经理: 经理-A, 产品数量: 6
    工程师: 工程师-A, 代码行数: 61280
    工程师: 工程师-B, 代码行数: 10353
    经理: 经理-B, 产品数量: 5
    工程师: 工程师-C, 代码行数: 65827
    

    4. 访问者模式

    4.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. (封装一些作用于某种数据结构中的各元素的操作, 它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。 )

    4.2 通用类图

    • Visitor 抽象访问者:抽象类或者接口,声明访问者可以访问哪些元素。
    • ConcreteVisitor 具体访问者:它影响访问者访问到一个类后该怎么干, 要做什么事情。
    • Element 抽象元素:接口或者抽象类,声明接受哪一类访问者访问。
    • ConcreteElement 具体元素:实现方法。
    • ObjectStruture 结构对象:元素产生者,一般容纳在多个不同类、不同接口的容器。

    4.3 通用代码

    抽象元素:

    public abstract class Element {
        // 定义业务逻辑
        abstract void doSomething();
        // 定义允许访问角色
        abstract void accept(IVisitor visitor);
    }
    

    具体元素:

    public class ConcreteElement1 extends Element{
        @Override
        void doSomething() {
    
        }
    
        @Override
        void accept(IVisitor visitor) {
            visitor.visit(this);
        }
    }
    
    public class ConcreteElement2 extends Element{
        @Override
        void doSomething() {
    
        }
    
        @Override
        void accept(IVisitor visitor) {
            visitor.visit(this);
        }
    }
    

    抽象访问者:

    public interface IVisitor {
        void visit(ConcreteElement1 ele1);
        void visit(ConcreteElement2 ele2);
    }
    

    具体访问者:

    public class Visitor implements IVisitor{
        @Override
        public void visit(ConcreteElement1 ele1) {
            ele1.doSomething();
        }
    
        @Override
        public void visit(ConcreteElement2 ele2) {
            ele2.doSomething();
        }
    }
    

    结构对象:

    public class ObjectStruture {
        public static Element createElement() {
            Random random = new Random();
            if (random.nextInt(100) > 50) {
                return new ConcreteElement1();
            } else {
                return new ConcreteElement2();
            }
        }
    }
    

    场景类:

    public class Client {
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                Element e1 = ObjectStruture.createElement();
                e1.accept(new Visitor());
            }
        }
    }
    

    4.4 优点

    1. 各角色职责分离,符合单一职责原则。

      通过UML类图和上面的示例可以看出来,Visitor、ConcreteVisitor、Element 、ObjectStructure,职责单一,各司其责。

    2. 具有优秀的扩展性。

      如果需要增加新的访问者,增加实现类 ConcreteVisitor 就可以快速扩展。

    3. 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化。

      员工属性(数据结构)和CEO、CTO访问者(数据操作)的解耦。

    4. 灵活性。

    4.5 缺点

    1. 具体元素对访问者公布细节,违反了迪米特原则。

      CEO、CTO需要调用具体员工的方法。

    2. 具体元素变更时导致修改成本大。

      变更员工属性时,多个访问者都要修改。

    3. 违反了依赖倒置原则,为了达到「区别对待」而依赖了具体类,没有用来抽象。

      访问者 visit 方法中,依赖了具体员工的具体方法。

  • 相关阅读:
    网站上线的过程
    PHP的四种基本算法
    YII框架第三方微博登录
    《正三角》《倒三角》
    PHP实现四种基本排序
    php实现快速排序
    iwebshop 简介
    收集的伪静态中经常使用的一些参数
    我与AI的相识
    phpstudy下的nginx服务器显示目录
  • 原文地址:https://www.cnblogs.com/babycomeon/p/14131462.html
Copyright © 2011-2022 走看看