访问者模式在设计模式中应该算是比较复杂的了,但也不能成为我们不学习的理由。
定义:封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
听起来就很绕,首先看数据结构这四个关键字,这就是不变的部分。比如说世界上只有男人和女人,当然(其他的忽略吧),这个就是很稳定的。如果是数据结构频繁变化的是不太适合用这个模式的。
那么变化的部分呢?
数据结构各个原色的操作,也就说变的是操作。
下面我举个例子,这也是我代码里面的例子。
我们现在的教育体系,从我们上学开始一直到大学结束,还是相对来说比较稳定的,为了方便写(其实是偷懒),我假设只有三种学校,而不是我们从幼儿园到初中,高中,大学的节奏。
而每一个阶段,都有要学习的相同科目。
比如说语文,从小学到大学可是都有的吧。
那么这个语文就是所说的操作。
当然,在这个例子里面其实有一个问题,学科其实也是很稳定的,但是我们假设学科是不稳定的,我现在只想到这个适应场景。
最开始,只有一个语文学科,但是每个阶段学习的语文都是不一样的,小学你可能只是认识字,初中高中你应该会造句写作文了阅读文章,大学可能学的是更加偏应用的应用文。
话不多说,我们上代码。
首先是抽象的学校类
public abstract class School {
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void acceptTeach(Subject subject);
}
接下来就是我们继承学校类的
小学类
public class PrimarySchool extends School {
public PrimarySchool() {
this.name="xiao xue";
}
@Override
public void acceptTeach(Subject subject) {
subject.primaryTeach(this);
}
}
中学类
public class MiddleSchool extends School{
public MiddleSchool() {
this.name="zhong xue";
}
@Override
public void acceptTeach(Subject subject) {
subject.middleTeach(this);
}
}
大学类
public class HighSchool extends School {
public HighSchool() {
this.name="da xue";
}
@Override
public void acceptTeach(Subject subject) {
subject.HighTeach(this);
}
}
我们发现累在抽象的父类里面有一个抽象的方法acceptTeach,接受一个学科
这里我把这个写成了接口,至于具体是接口还是抽象类各位根据实际情况判断。
所以学科类来了
public interface Subject {
void primaryTeach(School school);
void middleTeach(School school);
void HighTeach(School school);
}
现在我们来写实现类
public class Chinese implements Subject{
@Override
public void primaryTeach(School school) {
System.out.println(school.name+":一 二 三");
}
@Override
public void middleTeach(School school) {
System.out.println(school.name+":谁知盘中餐");
}
@Override
public void HighTeach(School school) {
System.out.println(school.name+":杨柳岸晓风残月");
}
}
现在我们写一个测试类
public class TestVisitor {
@Test
public void test() {
PrimarySchool primarySchool=new PrimarySchool();
MiddleSchool middleSchool=new MiddleSchool();
HighSchool highSchool=new HighSchool();
Subject subject=new Chinese();
primarySchool.acceptTeach(subject);
middleSchool.acceptTeach(subject);
highSchool.acceptTeach(subject);
}
}
我们现在看到,结果是什么呢?
xiao xue:一 二 三
zhong xue:谁知盘中餐
da xue:杨柳岸晓风残月
看到这里,大家有没有发现什么呢?
我们应该知道有一个设计原则
类应该对修改关闭,而对扩展开放。
我们现在想想,如果我们现在的学校需要增加了新的课程,比如说 数学。
那么我们需要做什么呢?
我们现在只需要增加一个实现学科类的数学类,并且实现接口里面的方法就好了
public class Math implements Subject{
@Override
public void primaryTeach(School school) {
System.out.println(school.name+":1+1");
}
@Override
public void middleTeach(School school) {
System.out.println(school.name+":log(2)");
}
@Override
public void HighTeach(School school) {
System.out.println(school.name+":csc x (sec x)");
}
}
而其他的类呢?
什么都不用改变!
看看我们的测试结果
public class TestVisitor {
@Test
public void test() {
PrimarySchool primarySchool=new PrimarySchool();
MiddleSchool middleSchool=new MiddleSchool();
HighSchool highSchool=new HighSchool();
//Subject subject=new Chinese();
Subject subject=new Math();
primarySchool.acceptTeach(subject);
middleSchool.acceptTeach(subject);
highSchool.acceptTeach(subject);
}
}
xiao xue:1+1
zhong xue:log(2)
da xue:csc x (sec x)
说了这么多,我们学习所谓访问者那么在这里面谁是访问者呢?
class A {
public void method1(){
System.out.println("我是A");
}
public void method2(B b){
b.showA(this);
}
}
class B {
public void showA(A a){
a.method1();
}
}
我们主要来看一下在类A中,方法method1和方法method2的区别在哪里,方法method1很简单,就是打印出一句“我是A”;方法method2稍微复杂一点,使用类B作为参数,并调用类B的showA方法。再来看一下类B的showA方法,showA方法使用类A作为参数,然后调用类A的method1方法,可以看到,method2方法绕来绕去,无非就是调用了一下自己的method1方法而已,它的运行结果应该也是“我是A
所以上面的例子我们也就看出来了 谁是访问者呢?
那么就是所有的操作类 也就是访问者,语文会访问所有的元素,所有的学校类
数学也会访问所有的元素累
而其实一般来说,操作累应该还有一个遍历的方法
就是把所有的元素都放在一个集合或者容器里面
访问者模式中对象结构存储了不同类型的元素对象,以供不同访问者访问。
我的例子只是用到了一个,没有这么写
其实这是一种双重抽象,
访问者模式包括两个层次结构,一个是访问者层次结构,提供了抽象访问者和具体访问者,一个是元素层次结构,提供了抽象元素和具体元素。
相同的访问者可以以不同的方式访问不同的元素,相同的元素可以接受不同访问者以不同访问方式访问。在访问者模式中,增加新的操作无须修改原有系统,系统具有较好的可扩展性
访问者模式并不是那么完美,它也有着致命的缺陷:增加新的元素类比较困难。通过访问者模式的代码可以看到,在访问者类中,每一个元素类都有它对应的处理方法,也就是说,每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。也就是说,在元素类数目不确定的情况下,应该慎用访问者模式。所以,访问者模式比较适用于对已有功能的重构,比如说,一个项目的基本功能已经确定下来,元素类的数据已经基本确定下来不会变了,会变的只是这些元素内的相关操作,这时候,我们可以使用访问者模式对原有的代码进行重构一遍,这样一来,就可以在不修改各个元素类的情况下,对原有功能进行修改。