设计模式之访问者模式

设计模式之访问者模式,第1张

设计模式之访问者模式 访问者模式 什么是访问者模式

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

原理图

注意点
  • 访问者模式符合单一职责原则,但是访问者关注了其他类的内部细节,不遵循迪米特法则,同时由于访问者依赖的是具体元素而不是抽象元素,违背了依赖倒转原则。
  • 访问者模式适用于具有稳定数据结构,功能需求经常改变的场景。轻易不会使用到访问者模式,但如果你的场景真的需要访问者模式的时候,那么他是非常合适的。
为什么要用访问者模式 问题背景

BJTU现可招收本科生和研究生(包括硕士研究生和博士研究生,统称为研究生),也就是说,该校有本科生和研究生两类学生可以访问学校的各种资源,为了描述简单,现假设学校有信息管理系统(MIS)与图书馆(LIB)两个资源。

java描述

MIS和LIB资源:

//父类,学校资源
public abstract class BJTUResource {
    public abstract void beVisited(String student);
}

//图书馆
public class LIB extends BJTUResource {
    @Override
    public void beVisited(String student) {
        if ("graduate".equals(student)) {
            System.out.println("研究生访问LIB");
        } else if ("undergraduate".equals(student)) {
            System.out.println("本科生访问LIB");
        }
    }
}

//信息管理系统
public class MIS extends BJTUResource {
    @Override
    public void beVisited(String student) {
        if ("graduate".equals(student)) {
            System.out.println("研究生访问MIS");
        } else if ("undergraduate".equals(student)) {
            System.out.println("本科生访问MIS");
        }
    }
}

调用一下:

public class Client {
    public static void main(String[] args) {
        LIB lib = new LIB();
        MIS mis = new MIS();
        lib.beVisited("graduate");
        lib.beVisited("undergraduate");
        mis.beVisited("graduate");
        mis.beVisited("undergraduate");
    }
}

结果:

研究生访问LIB
本科生访问LIB
研究生访问MIS
本科生访问MIS

可以发现,对于现在这个场景,我们可以很轻松的描述出来,并正确地运行出结果。

新的问题

上述场景我们可以满足,现在学校要开设一独立学院(注:独立学院是一个学校,招收的学生为独立学院的学生,学籍不为开设独立学院的学校),为了表明两所学校的关系,每年将推荐一些学生到原学校学习。

这些学生没有BJTU的学籍,但在BJTU学习,应当可以访问相关资源,约定其身份为:交流生。

对于这样的场景,我们上述的代码改起来有些费劲,学校每有一个资源,就需要修改一个资源内部的判断逻辑,增加一个交流生选项,如果不再有交流生或者有其他类型的学生,又需要再次更改,很显然,这是恼人的。


我们再来看这个结构,学校已建校多年,相关资源已十分完备,也就是说,对于资源而言,是基本固定的;学校和其他学校交流,可能出现不同的学生种类,比如联合培养等,学生的类型是不固定的。

这样的结构,就很适合使用访问者模式。

访问者模式实现

首先,我们肯定是需要摒弃掉这些if-else结构,学生类型不应使用字符串,而应该抽象出一个类:

//抽象的学生类,可以访问MIS和LIB
public abstract class Student {
    public abstract void visitMIS(MIS mis);

    public abstract void visitLIB(LIB lib);
}

//本科生
public class Undergraduate extends Student {

    @Override
    public void visitMIS(MIS mis) {
        System.out.println("本科生访问学校的MIS" + mis.hashCode());
    }

    @Override
    public void visitLIB(LIB lib) {
        System.out.println("本科生访问学校的LIB" + lib.hashCode());
    }
}

//研究生
public class Graduate extends Student {

    @Override
    public void visitMIS(MIS mis) {
        System.out.println("研究生访问学校的MIS" + mis.hashCode());
    }

    @Override
    public void visitLIB(LIB lib) {
        System.out.println("研究生访问学校的LIB" + lib.hashCode());
    }
}

//新增的交流生
public class Exchange extends Student {
    
    @Override
    public void visitMIS(MIS mis) {
        System.out.println("交流生访问学校的MIS" + mis.hashCode());
    }

    @Override
    public void visitLIB(LIB lib) {
        System.out.println("交流生访问学校的LIB" + lib.hashCode());
    }
}

学生类修改完成后,就应该着手于学校资源了:

//抽象的学校资源类,对于学校资源而言,只需要被访问就可以了
public abstract class BJTUResource {
    public abstract void accept(Student student);
}

//两个实现类使用了“双分派”方法,该方法将在后面展开叙述
//LIB
public class LIB extends BJTUResource {
    @Override
    public void accept(Student student) {
        student.visitLIB(this);
    }
}

//MIS
public class MIS extends BJTUResource {
    @Override
    public void accept(Student student) {
        student.visitMIS(this);
    }
}

调用一下:

public class Client {
    public static void main(String[] args) {
        LIB lib = new LIB();
        MIS mis = new MIS();
        lib.accept(new Graduate());
        lib.accept(new Undergraduate());
        lib.accept(new Exchange());
        mis.accept(new Graduate());
        mis.accept(new Undergraduate());
        mis.accept(new Exchange());
    }
}

结果:

研究生访问学校的LIB718231523
本科生访问学校的LIB718231523
交流生访问学校的LIB718231523
研究生访问学校的MIS745160567
本科生访问学校的MIS745160567
交流生访问学校的MIS745160567

此时我们可以发现,有没有交流生(Exchange)对两个资源而言毫无影响,资源只需要面对Student这一抽象类;对调用方几乎毫无影响,调用的时候只需要调用对用资源的accept方法,并告知其访问者类型即可。

增加新的学生类型,或者去除某一类型,对于整体影响很低。

双分派

所谓双分派,就是希望不管类如何变化,我们都可以找到期望的方法运行。双分派意味着执行的 *** 作取决于请求的种类和接收者的类型。

在上述的代码中,调用方把具体的学生种类作为参数传递给学校资源,这是第一次分派:

S t u d e n t → B J T U R e s o u r c e . a c c e p t Student rightarrow BJTUResource.accept Student→BJTUResource.accept

分派之后,具体的学校资源调用具体学生中访问该资源的具体方法,把自己作为参数进行传递,完成第二次分派:

B J T U R e s o u r c e → S t u d e n t . v i s i t BJTUResource rightarrow Student.visit BJTUResource→Student.visit

这样,我们就可以只调用一次accept方法,就找到了我们期望运行的对应方法。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5437887.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-11
下一篇 2022-12-11

发表评论

登录后才能评论

评论列表(0条)

保存