• 尚硅谷设计模式学习(十六)访问者模式


    以查看报表引入访问者模式

    年底,CEO和CTO开始评定员工一年的工作绩效,员工分为工程师和经理。

    • CTO关注工程师的代码量、经理的新产品数量。
    • CEO关注的是工程师的KPI和经理的KPI以及新产品数量。

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

    一、访问者模式

    1、基本介绍

    最复杂的设计模式,并且使用频率不高,《设计模式》的作者评价为:大多情况下,你不需要使用访问者模式,但是一旦需要使用它时,那就真的需要使用了。

    访问者模式是一种将数据操作和数据结构分离的设计模式。

    使用场景

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

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

    • Visitor:接口或者抽象类,定义了对每个 Element 访问的行为,它的参数就是被访问的元素,它的方法个数理论上与元素的个数是一样的,因此,访问者模式要求元素的类型要稳定,如果经常添加、移除元素类,必然会导致频繁地修改 Visitor 接口,如果出现这种情况,则说明不适合使用访问者模式。
    • ConcreteVisitor:具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。
    • Element:元素接口或者抽象类,它定义了一个接受访问者(accept)的方法,其意义是指每一个元素都要可以被访问者访问。
    • ElementA、ElementB:具体的元素类,它提供接受访问的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
    • ObjectStructure:定义当中所提到的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素提供访问者访问。

    2、代码实现

    员工基类,accept 方法表示接受访问者的访问,由子类具体实现。Visitor 是个接口,传入不同的实现类,可访问不同的数据。 

    1. // 员工基类
    2. // 提取经理和工程师的共同特征
    3. public abstract class Staff {
    4. public String name;
    5. public int kpi;
    6. public Staff(String name) {
    7. this.name = name;
    8. this.kpi = new Random().nextInt(10);
    9. }
    10. // 核心方法,接受Visitor的访问
    11. public abstract void accept(Visitor visitor);
    12. }

    工程师和产品经理

    1. public class Engineer extends Staff{
    2. public Engineer(String name){
    3. super(name);
    4. }
    5. @Override
    6. public void accept(Visitor visitor) {
    7. visitor.visit(this);
    8. }
    9. // 获取代码量
    10. public int getCodeLines(){
    11. return new Random().nextInt(10 * 10000);
    12. }
    13. }
    1. public class Manager extends Staff{
    2. public Manager(String name){
    3. super(name);
    4. }
    5. @Override
    6. public void accept(Visitor visitor) {
    7. visitor.visit(this);
    8. }
    9. //获取产品数量
    10. public int getProducts(){
    11. return new Random().nextInt(10);
    12. }
    13. }

    工程师是代码数量,经理是产品数量,他们的职责不一样,也就是因为差异性,才使得访问模式能够发挥它的作用。Staff、Engineer、Manager 3个类型就是对象结构,这些类型相对稳定,不会发生变化。
    然后将这些员工添加到一个业务报表类中,公司高层可以通过该报表类的 showReport 方法查看所有员工的业绩,具体代码如下:

    1. public class BusinessReport {
    2. private List list = new ArrayList();
    3. // 添加员工
    4. public void addStaff(Staff staff){
    5. list.add(staff);
    6. }
    7. //删除员工
    8. public void removeStaff(Staff staff){
    9. list.remove(staff);
    10. }
    11. // 展示报表
    12. public void show(Visitor visitor){
    13. for (Staff staff : list) {
    14. staff.accept(visitor);
    15. }
    16. }
    17. }

    下面看看 Visitor 类型的定义, Visitor 声明了两个 visit 方法,分别是对工程师和经理对访问函数,也就是说对于工程师和经理的访问会调用两个不同的方法,以此达成区别对待、差异化处理。

    1. public interface Visitor{
    2. // 访问工程师类型
    3. void visit(Engineer engineer);
    4. // 访问经理类型
    5. void visit(Manager manager);
    6. }

    CEO和CTO

    1. public class CEO implements Visitor {
    2. public void visit(Engineer engineer) {
    3. System.out.println(engineer.name + "的KPI ===> " + engineer.kpi);
    4. }
    5. public void visit(Manager manager) {
    6. System.out.println(manager.name + "的KPI ===> " + manager.kpi
    7. + " 产品数量 ===> " + manager.getProducts());
    8. }
    9. }
    1. public class CTO implements Visitor {
    2. public void visit(Engineer engineer) {
    3. System.out.println(engineer.name + "的代码量 ===> " + engineer.getCodeLines());
    4. }
    5. public void visit(Manager manager) {
    6. System.out.println(manager.name + "产品数量 ===> " + manager.getProducts());
    7. }
    8. }

    如果不使用 Visitor 模式,只通过一个 visit 方法进行处理,那么就需要在这个 visit 方法中进行判断,然后分别处理,这就导致了 if-else 逻辑的嵌套以及类型的强制转换,难以扩展和维护。

    下面是客户端代码

    1. public class Client {
    2. public static void main(String[] args) {
    3. BusinessReport businessReport = new BusinessReport();
    4. businessReport.addStaff(new Engineer("工程师1"));
    5. businessReport.addStaff(new Engineer("工程师2"));
    6. businessReport.addStaff(new Engineer("工程师3"));
    7. businessReport.addStaff(new Engineer("工程师4"));
    8. businessReport.addStaff(new Engineer("工程师5"));
    9. businessReport.addStaff(new Manager("产品经理1"));
    10. businessReport.addStaff(new Manager("产品经理2"));
    11. businessReport.addStaff(new Manager("产品经理3"));
    12. System.out.println("-----CEO看报表-----");
    13. businessReport.show(new CEO());
    14. System.out.println("-----CTO看报表-----");
    15. businessReport.show(new CTO());
    16. }
    17. }

    输出

    1. -----CEO看报表-----
    2. 工程师1的KPI ===> 6
    3. 工程师2的KPI ===> 7
    4. 工程师3的KPI ===> 5
    5. 工程师4的KPI ===> 7
    6. 工程师5的KPI ===> 1
    7. 产品经理1的KPI ===> 9 产品数量 ===> 2
    8. 产品经理2的KPI ===> 1 产品数量 ===> 1
    9. 产品经理3的KPI ===> 9 产品数量 ===> 7
    10. -----CTO看报表-----
    11. 工程师1的代码量 ===> 37256
    12. 工程师2的代码量 ===> 70150
    13. 工程师3的代码量 ===> 7235
    14. 工程师4的代码量 ===> 37880
    15. 工程师5的代码量 ===> 55440
    16. 产品经理1产品数量 ===> 8
    17. 产品经理2产品数量 ===> 2
    18. 产品经理3产品数量 ===> 8

    在上述示例中,Staff 扮演了 Element 角色,而 Engineer 和 Manager 都是 ConcreteElement;CEOVisitor 和 CTOVisitor 都是具体的 Visitor 对象;而 BusinessReport 就是 ObjectStructure;Client就是客户端代码。

    访问者模式最大的优点就是增加访问者非常容易,我们从代码中可以看到,如果要增加一个访问者,只要新实现一个 Visitor 接口的类,从而达到数据对象与数据操作相分离的效果。

    二、访问者模式的注意事项和细节

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

    访问者模式的优点

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

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

    2、具有优秀的扩展性

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

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

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

    4、灵活性

    访问者模式的缺点

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

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

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

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

    3、违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有以来抽象

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

    参考文章:访问者模式一篇就够了 - 简书 (jianshu.com)

  • 相关阅读:
    Fabric.js 复制粘贴元素
    性能与效果平衡:选择适合项目的直播实时美颜SDK
    Java集合之Set集合
    卸载Gitlab,导入新的备份
    GitLab CI/CD
    FreeRTOS-链表的源码解析
    TCP的连接套接口哈希表初始化
    初次使用腾讯云,解决只能使用webshell连接,不能使用ssh连接。
    使用 Sa-Token 完成踢人下线功能
    微信小程序组件、web-view、h5之间交互
  • 原文地址:https://blog.csdn.net/qq_51409098/article/details/126930115