访问者模式表示一个作用于某对象结构中的各种元素的 *** 作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新 *** 作。
原理图 注意点- 访问者模式符合单一职责原则,但是访问者关注了其他类的内部细节,不遵循迪米特法则,同时由于访问者依赖的是具体元素而不是抽象元素,违背了依赖倒转原则。
- 访问者模式适用于具有稳定数据结构,功能需求经常改变的场景。轻易不会使用到访问者模式,但如果你的场景真的需要访问者模式的时候,那么他是非常合适的。
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方法,就找到了我们期望运行的对应方法。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)