访问者模式
定义
访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
简单来说就是在结构不发生改变的情况下对于内部元素进行动态改变的动作。
与观察者模式区分,访问者模式是固定了模式状态,根据访问者的不同而做出不同的动作。
特点
-
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。
-
访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。
-
访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。
应用
代码讲解
举个例子:
景区公园等售票会根据年龄段的不同而分别开出儿童票、成人票和老年票,接下来使用代码进行模拟。
- 首先我们有个角色类接口
Role
,让他有一个接受访问接口的方法accept(Visitor v)
:
public interface Role {
void accept(Visitor v);
}
- 写一个访问者接口
Visitor
,他有对应的访问方法visit()
,参数分别对Role
和三个角色:
public interface Visitor {
void visit(Role r);
void visit(Child c);
void visit(Adult adult);
void visit(Aged aged);
}
- 分别写出我们的三个角色,让他们实现
Role
接口。有了这个接口,我们的角色类就可以只关注这个接口即可:
class Child implements Role {
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
class Adult implements Role {
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
class Aged implements Role {
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
- 接下来我们写一个公园
Park
类也实现Visitor
接口,分别根据角色实现其收费方法:
public class Park implements Visitor{
@Override
public void visit(Role r) {
r.accept(this);
}
@Override
public void visit(Child c) {
System.out.println("儿童半价");
}
@Override
public void visit(Adult adult) {
System.out.println("成人全价");
}
@Override
public void visit(Aged aged) {
System.out.println("老年人不收费");
}
}
- 主程序就可以直接调用角色和公园,然后只需修改访问对象就可以对应不同的动作方法:
public class Main {
public static void main(String[] args) {
Role r = new Adult();//只需修改角色对象
Park p = new Park();
p.visit(r);
}
}
运行结果:
通过上述代码就已经完成了一个简单的访问者模式,方法根据不同参数的多态选择类似策略模式
,但都实现了访问者接口。
当系统中存在类型数量稳定(固定)的一类数据结构时,可以使用访问者模式方便地实现对该类型所有数据结构的不同操作,而又不会对数据产生任何副作用(脏数据)。
使用场景
简而言之,就是当对集合中的不同类型数据(类型数量稳定)进行多种操作时,使用访问者模式。
通常在以下情况可以考虑使用访问者(Visitor)模式。
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
- 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
编译器
有很大一部分编译器都是类似访问者模式的结构,感兴趣的同学可以自行查看GoF的《设计模式》和源码文档。