简易理解设计模式之:访问者模式——员工考核例子

介绍:

访问者模式属于行为型模式。表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类前提下定义作用于这些元素的新操作。

它的基本思想是:将数据操作与数据结构分离,将一些稳定的对象的类拥有一个accept方法用来接收访问者访问。

类图:
访问者模式UML类图
• Visitor(抽象访问者):定义每一个元素(Element)访问的行为。
• ConcreteVisitor(具体访问者):需要对每一个元素访问时所产生具体行为。
• Element(抽象元素):定义一个接受访问者的方法accept(Visitor v)。指每一个元素都可以被访问者访问。
• ElementA、ElementB(具体元素):给接受访问者方法accept(Visitor v)的具体实现。
• ObjectStructure(对象结构):管理元素集合

用法:
• 对象结构比较稳定,但经常需要在此对象结构上定义新的操作。
• 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望再增加新操作时修改这些类。

例子:
年终时,公司高层会根据每个员工这一年做出的功劳发年终奖,对基层员工和对管理层员工发的奖金也是不一样。我们模拟一下这个例子:

设计思路:为了简单模拟这个过程,我们把员工分为工程师和经理,公司高层分为CTO和CEO。CTO只关注代码行数和代码质量,CEO只关注业绩。

1、第一版代码:
1.1、员工基类

public abstract class Staff {
    public String name;
    public int kpi;

    public Staff(String name){
        this.name = name;
        this.kpi = new Random().nextInt(10);
    }

    public abstract void evaluate(String s); //高层调用评价
}

我们用面向对象的知识,抽象出公共部分。evaluate(String s)方法会传入ceo或cto。

1.2、经理类

public class Manager extends Staff {
    private int products; //产品数量
    public Manager(String name) {
        super(name);
        products = new Random().nextInt(10);
    }

    public int getProducts() {
        return products;
    }

    @Override
    public void evaluate(String s){
        if (s.equals("ceo")){
            System.out.println("经理:" + name + ",新产品数量:" + getProducts());
        } else if (s.equals("cto")){
            System.out.println("经理:" + name + ",KPI:" + kpi + ",新产品数量:" + getProducts());
        }
    }

}

经理类新增属性产品数量。被评价时会根据产品数量和kpi去考核。

1.3、工程师类

public class Engineer extends Staff {
    public Engineer(String name) {
        super(name);
    }

    public int getCodeLines(){
        return new Random().nextInt(10 * 10000);
    }

    @Override
    public void evaluate(String s){
        if (s.equals("ceo")){
            System.out.println("工程师:" + name + ",KPI:" + kpi);
        } else if (s.equals("cto")){
            System.out.println("工程师:" + name + ",代码数量:" + getCodeLines());
        }
    }
}

程序员的属性是代码行数,被评价时通常以代码行数作为依据。

1.4、报表类

public class BusinessReport {
    List<Staff> mStaffs = new ArrayList<>();

    public BusinessReport(){
        mStaffs.add(new Manager("王经理"));
        mStaffs.add(new Manager("陈经理"));
        mStaffs.add(new Engineer("Android开发者"));
        mStaffs.add(new Engineer("iOS开发者"));
        mStaffs.add(new Engineer("php开发者"));
    }

    public void showReport(String s){
        for (Staff staff : mStaffs){
            staff.evaluate(s);
        }
    }
}

1.5、实现与测试

public class Test {
    public static void main(String[] args) {
        //构建报表
        BusinessReport businessReport = new BusinessReport();

        System.out.println("====给CEO看的报表====");

        businessReport.showReport("ceo");

        System.out.println("====给CTO看的报表====");

        businessReport.showReport("cto");

    }
}
====给CEO看的报表====
经理:王经理,新产品数量:2
经理:陈经理,新产品数量:3
工程师:Android开发者,KPI:5
工程师:iOS开发者,KPI:7
工程师:php开发者,KPI:4
====给CTO看的报表====
经理:王经理,KPI:4,新产品数量:2
经理:陈经理,KPI:1,新产品数量:3
工程师:Android开发者,代码数量:23990
工程师:iOS开发者,代码数量:69770
工程师:php开发者,代码数量:770

我们简单分析一下这个做法:这样做比较简单也实现了功能,但没用到设计模式。如果评分的高管加多一个副总,那我们需要在Engineer类和Manager类的分支那里加多一个判断,违反了开闭原则。

2、访问者模式实现:
我们运用访问者模式的套路实现一下,我们根据第一个例子改版一下:
数据结构(元素):Manager类和Engineer类,是一个比较稳定的结构,并且各自有自己的属性。
数据操作(访问者):将evaluate()评价操作分离出来。

2.1、抽象元素类

public abstract class Staff {
    public String name;
    public int kpi;

    public Staff(String name){
        this.name = name;
        this.kpi = new Random().nextInt(10);
    }

    public abstract void accept(Vistor vistor);  
}

定义一个接受访问者的方法accept(Visitor v)方法。将1.1evaluate()方法干掉。此类作为抽象元素类。

2.2、具体元素类

public class Manager extends Staff {
    private int products; //产品数量
    public Manager(String name) {
        super(name);
        products = new Random().nextInt(10);
    }

    @Override
    public void accept(Vistor vistor) {
        vistor.vist(this);
    }

    public int getProducts() {
        return products;
    }
}
public class Engineer extends Staff {
    public Engineer(String name) {
        super(name);
    }

    @Override
    public void accept(Vistor vistor) {
        vistor.vist(this);
    }

    public int getCodeLines(){
        return new Random().nextInt(10 * 10000);
    }
}

根据上文1.2、1.3的两个类,我们做出了一些改变,实现accept()方法,将自身传给Visitor去进行数据操作。

2.3、抽象访问者类

public interface Vistor {
    void vist(Engineer engineer);
    void vist(Manager manager);
}

定义每一个元素(Element)访问的行为。

2.4、具体访问者类

public class CEOVistor implements Vistor {
    @Override
    public void vist(Engineer engineer) {
        System.out.println("工程师:" + engineer.name + ",KPI:" + engineer.kpi);
    }

    @Override
    public void vist(Manager manager) {
        System.out.println("经理:" + manager.name + ",KPI:" + manager.kpi + ",新产品数量:" + manager.getProducts());
    }
}
public class CTOVistor implements Vistor {
    @Override
    public void vist(Engineer engineer) {
        System.out.println("工程师:" + engineer.name + ",代码数量:" + engineer.getCodeLines());
    }

    @Override
    public void vist(Manager manager) {
        System.out.println("经理:" + manager.name + ",产品数量:" + manager.getProducts());
    }
}

每一个访问者对所有元素的访问逻辑的具体行为。相当于我们将上文1.2、1.3的evaluate()方法抽离成类了。

2.5、报表类ObjectStructure

public class BusinessReport {
    List<Staff> mStaffs = new ArrayList<>();

    public BusinessReport(){
        mStaffs.add(new Manager("王经理"));
        mStaffs.add(new Manager("陈经理"));
        mStaffs.add(new Engineer("Android开发者"));
        mStaffs.add(new Engineer("iOS开发者"));
        mStaffs.add(new Engineer("php开发者"));
    }

    /**
     *
     * @param vistor
     */
    public void showReport(Vistor vistor){
        for (Staff staff : mStaffs){
            staff.accept(vistor);
        }
    }
    
}

修改了showReport方法,将访问者和元素做一个关联。

2.6、测试与实现

public class Test {
    public static void main(String[] args) {
        //构建报表
        BusinessReport businessReport = new BusinessReport();

        System.out.println("====给CEO看的报表====");

        businessReport.showReport(new CEOVistor());

        System.out.println("====给CTO看的报表====");

        businessReport.showReport(new CTOVistor());

    }
}

实现效果跟上文1.5中一样,下面我们分析一下:
整个过程把数据操作部分抽象出Visitor类,数据结构部分作为元素类定义一个accept(this)的方法让访问者访问。

如果评分的高管增加一个副总,可以使不改变各元素的类前提下定义作用于这些元素的新操作,也就是副总的评分我不用在员工类内部的evaluate(String s)方法增加多一个副总的分支,但需要新建一个副总的Visitor去实现。

总结:
此模式使用频率不高,知道怎么用就好。这个模式最大的特点就是数据结构和数据操作的分离,我们追求数据结构稳定性的同时将数据操作抽象出去,Visitor的改动成本和新增元素的成本也是挺大的,还是不要滥用好。

感谢您的阅读~

推荐阅读

基础篇:
设计模式前篇之——UML类图必会知识点
设计模式前篇之——一起过一下面向对象的概念
创建型模式:
简易理解设计模式之:简单工厂模式——来试试接入支付功能
简易理解设计模式之:工厂方法模式——数据存储例子
简易理解设计模式之:抽象工厂模式——更换数据库例子
简易理解设计模式之:建造者模式——学习使用“链式调用”
简易理解设计模式之:原型模式——深、浅拷贝的概念
简易理解设计模式之:单例模式——单例模式的几种常用写法
结构型模式:
简易理解设计模式之:适配器模式——Android列表视图控件设计方式
简易理解设计模式之:桥接模式——穿衣服经典案例2
简易理解设计模式之:组合模式——实现View中的树状结构
简易理解设计模式之:装饰模式——穿衣服经典案例
简易理解设计模式之:外观模式——第三方SDK的帮助类
简易理解设计模式之:享元模式——五子棋游戏例子
简易理解设计模式之:代理模式——iOS视图控件设计方式
行为型模式:
简易理解设计模式之:策略模式——优化一下支付功能
简易理解设计模式之:模板方法模式——Android中的BaseActivity基类
简易理解设计模式之:观察者模式——监听与回调
简易理解设计模式之:状态模式——优化登录操作
简易理解设计模式之:备忘录模式——Word文档的工作原理
简易理解设计模式之:迭代器模式——遍历对象的好帮手
简易理解设计模式之:命令模式——实现命令的参数化配置
简易理解设计模式之:责任链模式——OA中请假流程示例
简易理解设计模式之:中介者模式——多人聊天室例子
简易理解设计模式之:解释器模式——语言和文法
简易理解设计模式之:访问者模式——员工考核例子

posted @ 2022-11-21 18:56  TwcatL_tree  阅读(17)  评论(0编辑  收藏  举报