小白设计模式:访问者模式

作者: CodeInfo | 来源:发表于2018-11-25 15:33 被阅读0次

定义

可作用于对象结构中各个元素,在不改变各元素类的前提下,定义作用于这些元素新操作方法的一种行为型设计模式。

主要组成

抽象访问者(Visitor): 声明出对对象结构中每一个具体元素的访问方法visit,传入Concrete Element对象作为参数

具体访问者(Concrete Visitor):
实现各种visit方法,调用具体元素对象完成对应的各种操作

元素(Element):
定义出抽象accept方法,用于接收Visitor对象参数

具体元素(Concrete Element):
实现accept操作,该操作一般用于操作执行Visitor.visit方法,将自身作为参数传入

对象结构(Object structure):
各个元素构成的一个整体,提供能够让访问者访问所有元素的接口。可以是集合(比如List),或者是复合的类对象。

UML图

image

框架代码

抽象访问者:
需要依赖于具体的元素类,而不是抽象元素类,以避免if-else之类的嵌套和类型判断

public interface Visitor {
    
    void visit(ConcreteElementA elementA);
    void visit(ConcreteElementB elementB);
}

具体访问者:

public class ConcreteVisitorA implements Visitor{

    @Override
    public void visit(ConcreteElementA elementA) {
        // use elementA to do something
    }

    @Override
    public void visit(ConcreteElementB elementB) {
        // use elementB to do something
        
    }
}

public class ConcreteVisitorB implements Visitor{

    @Override
    public void visit(ConcreteElementA elementA) {
        // use elementA to do something
    }

    @Override
    public void visit(ConcreteElementB elementB) {
        // use elementB to do something
        
    }
}

抽象元素(可能接口或者抽象类):
由各个具体元素类实现accept接口,用于使得Visitor依赖于具体元素类

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

具体元素:

public class ConcreteElementA implements Element{

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

}

public class ConcreteElementB implements Element{

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

}

Client中简单调用:

    List<Element> elements = new ArrayList<>();
    elements.add(new ConcreteElementA());
    elements.add(new ConcreteElementB());
    elements.add(new ConcreteElementA());
    
    ConcreteVisitorA concreteVisitorA = new ConcreteVisitorA();
    ConcreteVisitorB concreteVisitorB = new ConcreteVisitorB();
    for (Element element : elements) {
        element.accept(concreteVisitorA);
        element.accept(concreteVisitorB);
    }

具体例子

公司对员工进行考核,员工存在工程师和产品经理,考核的评审有CEO和CTO,CTO只关注工程师的代码量和产品经理的新产品数量。而CEO则关注工程师的KPI和产品经理的KPI及新产品数量。从这边可以看出CEO和CTO对员工考核的关注点不一样,这就需要对员工类型进行处理,就可以用到访问者模式(员工分类结构也是很稳定不变的)。(来自<<Android源码设计模式解析与实践>>)

UML

image

代码

员工Staff:

public abstract class Staff {
    String name;
    public Staff(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    public abstract int getKPI();
    public abstract void accept(Visitor visitor);
}

工程师Engineer:

public class Engineer extends Staff{

    public Engineer(String name) {
        super(name);
        
    }

    @Override
    public int getKPI() {
        return 9;
    }
    
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    
    public int getCodeLines() {
        return 9999;
    }

}

产品经理Manager:

public class Manager extends Staff{

    public Manager(String name) {
        super(name);
        
    }

    @Override
    public int getKPI() {
        return 10;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    
    public int getProductCount() {
        return 5;
    }
}

访问者Visitor:

public interface Visitor {
    
    void visit(Engineer engineer);
    void visit(Manager manager);
}

CEO:

public class CEOVisitor implements Visitor{

    @Override
    public void visit(Engineer engineer) {
        System.out.println("CEOVisitor 考核工程师"+ engineer.getName() + ",KPI:" + engineer.getKPI());
        
    }

    @Override
    public void visit(Manager manager) {
        // TODO Auto-generated method stub
        System.out.println("CEOVisitor 考核产品经理"+ manager.getName() + ",KPI:" + manager.getKPI() + ",产品数:" + manager.getProductCount());
    }

}

CTO:

public class CTOVisitor implements Visitor{
    
    @Override
    public void visit(Engineer engineer) {
        System.out.println("CTOVisitor 考核工程师"+ engineer.getName() + ",CodeLines:" + engineer.getCodeLines());
        
    }

    @Override
    public void visit(Manager manager) {
        // TODO Auto-generated method stub
        System.out.println("CTOVisitor 考核产品经理"+ manager.getName() + ",产品数:" + manager.getProductCount());
    }
}

BussinessReport(对应Object structure):

public class BussinessReport {
    List<Staff> staffs = new ArrayList<>();

    public BussinessReport() {
        staffs.add(new Engineer("小张"));
        staffs.add(new Manager("小王"));
        
    }
    
    public void showReport(Visitor visitor) {
        for (Staff staff : staffs) {
            staff.accept(visitor);
        }
    }
}

Client调用:

//构建报表
BussinessReport bussinessReport = new BussinessReport();
//给CEO看
bussinessReport.showReport(new CEOVisitor());
//给CTO看
bussinessReport.showReport(new CTOVisitor());

假设不使用访问者模式

如果不使用访问者模式的话,也就不存在Visitor继承体系,以上述具体例子为例则需要对Staff的各种类型进行判断:
假设在ReportUtils处理:

public class ReportUtils {
    public static void visitCEO(Staff staff) {
        if (staff instanceof Manager) {
            Manager manager = (Manager)staff;
            System.out.println("考核产品经理"+ manager.getName() + ",产品数:" + manager.getProductCount());
        } else if(staff instanceof Engineer) {
            Engineer engineer = (Engineer)staff;
            System.out.println("考核工程师"+ engineer.getName() + ",KPI:" + engineer.getKPI());
        }
    }
    
    public static void visitCTO(Staff staff) {
        if (staff instanceof Manager) {
            Manager manager = (Manager)staff;
            System.out.println("考核产品经理"+ manager.getName() + ",产品数:" + manager.getProductCount());
        } else if(staff instanceof Engineer) {
            Engineer engineer = (Engineer)staff;
            System.out.println("考核工程师"+ engineer.getName() + ",CodeLines:" + engineer.getCodeLines());
        }
    }
}

这就会导致出现一大堆的if -else嵌套和类型转换,当类型较多的时候就会难以扩展和维护,并且当关注层面不同,重复的判断会很多,难以维护后续持续扩展新的操作(visitXX,visitXXX...)。而使用访问者Visitor模式,能够通过accept-visit方法的配套使用使得不同关注面的操作可以分离,并且去除一大堆嵌套if-else和类型转换的判断,灵活性更好,可扩展性高,更易于维护。

总结

优点

角色职责分离,符合单一职责原则;

允许对组合结构中的各个元素加入新的操作,而不需要修改结构本身,增加新操作相对容易;

集中管理访问者所进行操作的代码;

缺点

具体元素元素对访问者公布细节,违反了迪米特原则(一个类对自己需要耦合或调用的类知道的最少);

增加删减具体元素时修改成本过大,需要修改Visitor继承体系;

为了达到"区别对待"而依赖具体类而不是抽象,违反了"依赖倒置"原则(高层模块不应该依赖低层模块,两者都应该依赖抽象);

应用场景

1、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。

2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
注意事项:访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。

微信公众号

image

相关文章

网友评论

    本文标题:小白设计模式:访问者模式

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