访问者模式
访问者顾名思义,就是要访问一个对象的内部结构,那他是怎么样访问的呢,我们看看定义
- 定义
封装一些作用于某种数据结构中的个元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素新的操作()。
- 定义解释(若对定义的解释不好理解,请对照举得例子来加深理解)
-
初看访问者的定义,我们很难明白他在说什么,我们可以对定义这么理解:有这么一个操作,它是作用于一些元素(要操作的对象)之上的,而这些元素属于某一个对象结构(可以理解为为一个对象里面的集合)。同时这个操作是在不改变各元素类的前提下,在这个前提下定义新操作是访问者模式精髓中的精髓。
-
访问者模式的基本想法是,软件系统中拥有一个由许多对象构成的、比较稳定的对象结构,这些对象的类都拥有一个 accept 方法用来接受访问者对象的访问(即方法的入参是访问者类型)。访问者是一个接口,它拥有一个 visit 方法,这个方法对访问到的对象结构中不同类型的元素做出不同的处理(即这个vist方法的入参是被访问对象类型,他可以调用被访者内部结构)。在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施 accept 方法,在每一个元素的 accept 方法中会调用访问者的 visit 方法,从而使访问者得以处理对象结构的每一个元素,我们可以针对对象结构设计不同的访问者类来完成不同的操作,达到区别对待的效果。
- 访问者模式类图
Visitor(访问者):一般是接口或者抽象类,定义了对每一个元素(Element即被访问者)访问行为,一般情况有几个元素就会有几个方法,方法参数就是被访问者类型。因此访问者模式要求元素类族要稳定,如果经常添加、移除元素类,必然会导致频繁地修改Visitor接口,如果这样则不适合使用访问者模式。
ConcreteVisitorA、ConcreteVisitorB:具体的访问类,它需要给出对每一个元素类访问时所产生的具体行为。
Element(被访问者):元素接口或者抽象类,它定义了一个接受访问者的方法(Accept),其意义是指每一个元素都要可以被访问者访问。
ElementA、ElementB:具体的元素类,它提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
ObjectStructure:定义当中所说的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素供访问者访问。
静态分派以及动态分派
变量被声明时的类型叫做变量的静态类型(Static Type),有些人又把静态类型叫做明显类型(Apparent Type);而变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type)。比如:
List list = null;
list = new ArrayList();
声明了一个变量list,它的静态类型(也叫明显类型)是List,而它的实际类型是ArrayList。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。静态分派(Static Dispatch)发生在编译时期,分派根据静态类型信息发生。
静态分派
静态分派就是按照变量的静态类型进行分派,从而确定方法的执行版本,静态分派在编译时期就可以确定方法的版本。而静态分派最典型的应用就是方法重载
上面领导人实现的visit
就是方法的重载(根据参数的类型判断调用哪个方法)
@Override
public void visit(Guangzhou guangzhou) {
guangzhouEconomics = guangzhou.getEconomics();
}
在静态分派判断的时候,我们根据多个判断依据(即参数类型和个数)判断出了方法的版本,那么这个就是多分派的概念,因为我们有一个以上的考量标准,也可以称为宗量。所以JAVA是静态多分派的语言。
动态分派
对于动态分派,与静态相反,它不是在编译期确定的方法版本,而是在运行时才能确定。而动态分派最典型的应用就是多态的特性
请看这么一段代码:
interface Person{
void test();
}
class Man implements Person{
public void test(){
System.out.println("男人");
}
}
class Woman implements Person{
public void test(){
System.out.println("女人");
}
}
public class Main {
public static void main(String[] args) {
Person man = new Man();
Person woman = new Woman();
man.test();
woman.test();
}
}
- 在这段代码中就无法根据man和woman的静态类型去判断了,他们的静态类型都是Person接口,根本无从判断。
- 显然,产生的输出结果,就是因为test方法的版本是在运行时判断的,这就是动态分派。
- 动态分派判断的方法是在运行时获取到man和woman的实际引用类型,再确定方法的版本,而由于此时判断的依据只是实际引用类型,只有一个判断依据,所以这就是单分派的概念,这时我们的考量标准只有一个宗量,即变量的实际引用类型。相应的,这说明JAVA是动态单分派的语言。
- 访问者模式的伪动态双分派
访问者模式中使用的是伪动态双分派,所谓的动态双分派就是在运行时依据两个实际类型去判断一个方法的运行行为,而访问者模式实现的手段是进行了两次动态单分派来达到这个效果
访问者伪动态双分派代码
for (GuangDongCity guangDongCity : guangDongCityList) {
guangDongCity.accept(leader);
}
这里就是依据guangDongCity和leader两个实际类型决定了leader方法的版本,从而决定了accept方法的动作。
分析accept
的调用过程
- 当调用accept方法时,根据实际的GuangDongCity 的实际类型来觉定调用是是广州的accept还是深圳的accept.
- 当accept确定时,假如调用的是广州的accept方法即调用的下面这段代码
@Override
public void accept(Leader leader) {
leader.visit(this);
}
- 此时该方法的this就是Guangzhou,所以此时
leader.visit(this)
调用就是Leader接口的visit(Guangzhou guangzhou)
方法,但是因为Leader是接口,我们需要调用其实现类的方法,我们可以更加Lead的实际类型来确定调用哪一个实现类的方法,如此一来,就完成了动态双分派的过程。