设计模式之访问者模式

小明公司的福利

小明作为一个程序员,难免不经常加班,所以他这一年的年假也没有机会享受,但是小明的公司福利很好,对于公司没有休年假的员工作相应的补偿

具体的补偿根据员工的入职时间和职级来的,我们先看看小明公司的员工

public class Staff {

    String name;
    int workYear;
    int level;
    int vacationDay;

    public Staff(String name, int workYear, int level, int vacationDay) {
        this.name = name;
        this.workYear = workYear;
        this.level = level;
        this.vacationDay = vacationDay;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getWorkYear() {
        return workYear;
    }

    public void setWorkYear(int workYear) {
        this.workYear = workYear;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public int getVacationDay() {
        return vacationDay;
    }

    public void setVacationDay(int vacationDay) {
        this.vacationDay = vacationDay;
    }
}

员工集合类

public class Staffs {

    Map<String, Staff> staffMap = new HashMap<>(20);

    public Staffs() {

        Staff xiaoming = new Staff("xiaoming", 2, 1, 5);
        Staff zhuguan = new Staff("zhuguan", 4, 2, 4);
        Staff jingli = new Staff("jingli", 5, 4, 1);

        staffMap.put(xiaoming.getName(), xiaoming);
        staffMap.put(zhuguan.getName(), zhuguan);
        staffMap.put(jingli.getName(), jingli);
    }
}

现在我们要开始补偿员工了,规则是:年限的系数为1,职级的系数也为1,然后乘以每天50元的补偿。比如小明,如果还有1天没休,那么他这一天应该得到的补偿为:(1x2+1x1)x50,就是一天150块钱的补偿,所以对于小明来说,他还有5天的年假,所以他能得到750元的补偿。那么这个功能怎么加呢?由于是小明负责这个程序,他在员工集合类中加入了一个方法:

public class Staffs {

    Map<String, Staff> staffMap = new HashMap<>(20);

    public Staffs() {
        Staff xiaoming = new Staff("xiaoming", 2, 1, 5);
        Staff zhuguan = new Staff("zhuguan", 4, 2, 4);
        Staff jingli = new Staff("jingli", 5, 4, 1);

        staffMap.put(xiaoming.getName(), xiaoming);
        staffMap.put(zhuguan.getName(), zhuguan);
        staffMap.put(jingli.getName(), jingli);
    }

    void printCompensation() {
        for (Staff staff : staffMap.values()) {
            System.out.println(staff.getName() + " 应该得到" + ((staff.getWorkYear() + staff.getLevel()) * 50 * staff.getVacationDay()) + "元补偿");
        }
    }
}

得到的结果:

xiaoming 应该得到750元补偿
jingli 应该得到450元补偿
zhuguan 应该得到1200元补偿

小明开开心心的去找主管交代码准备领钱了,主管看了小明的代码说,你这样做违背了开闭原则。主管接着说,在没有这个福利之前,我们的员工类和员工集合类一直都没有任何的修改,而因为这个福利,加入了一个新的方法,这不是最关键的,关键是,如果明年没有这个福利了或者这个福利的补偿算法变了,你又必须修改员工集合类(Staffs),所以对于福利这一块,它是变动的。小明着急的问,那怎么办呢,主管说,对于这种结构比较固定的类,如果我们需要利用他们的元素去做一些其他事情,并且这些事情是变化的,我们需要将它独立出来,这次我们需要的是访问者模式

可被访问者接口:

public interface Visitable {
    void accept(Visitor visitor);
}

我们的员工实现我们的Visitable接口:

public class Staff implements Visitable {

    String name;
    int workYear;
    int level;
    int vacationDay;

    public Staff(String name, int workYear, int level, int vacationDay) {
        this.name = name;
        this.workYear = workYear;
        this.level = level;
        this.vacationDay = vacationDay;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getWorkYear() {
        return workYear;
    }

    public void setWorkYear(int workYear) {
        this.workYear = workYear;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public int getVacationDay() {
        return vacationDay;
    }

    public void setVacationDay(int vacationDay) {
        this.vacationDay = vacationDay;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

增加一个访问者接口:

public interface Visitor {
    void visit(Staff staff);
}

实现一个具体的访问者,比如我们的计算补偿的访问者:

public class CompensationVisit implements Visitor {
    @Override
    public void visit(Staff staff) {
        System.out.println(staff.getName() + " 应该得到" + ((staff.getWorkYear() + staff.getLevel()) * 50 * staff.getVacationDay()) + "元补偿");
    }
}    

使用我们的模式来计算大家的补偿

public class Client {
    public static void main(String[] args) {
        CompensationVisit compensationVisit = new CompensationVisit();
        Staffs staffs = new Staffs();
        for (Staff staff : staffs.staffMap.values()) {
            staff.accept(compensationVisit);
        }
    }
}

执行结果是和上面一样的:

xiaoming 应该得到750元补偿
jingli 应该得到450元补偿
zhuguan 应该得到1200元补偿

如果明年我们的福利算法变了,我们只需要在Staff的accept方法中传入另一个不同实现的访问者即可,这就是我们的访问者模式

定义

封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作

访问者模式

角色

  • 抽象元素类
  • 元素类
  • 抽象访问者
  • 访问者
  • 结构对象

访问者模式的适用场景

假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者中去

假如一组对象中,存在着相似的操作,为了避免出现大量重复的代码,也可以将这些重复的操作封装到访问者中去

坚持原创分享,您的支持将鼓励我不断前行!