访问者模式(Visitor)
访问者模式解决的问题是, 在不改变现有类的前提下, 动态地为这些类增加新的操作
假如我有如下类实现:
java
public class VisitorPattern {
public static void main(String[] args) {
Teacher teacher = new Teacher();
Programmer programmer = new Programmer();
Cooker cooker = new Cooker();
for(Human human : humanList) {
human.eat();
human.walk();
}
}
}
interface Human {
void eat();
void walk();
}
class Teacher implements Human {
@Override
public void eat() {
System.out.println("[teacher] 吃教师员工餐");
}
@Override
public void walk() {
System.out.println("[teacher] 走入教室");
}
}
class Programmer implements Human {
@Override
public void eat() {
System.out.println("[programmer] 吃外卖");
}
@Override
public void walk() {
System.out.println("[Programmer] 走向工位");
}
}
class Cooker implements Human {
@Override
public void eat() { System.out.println("[cooker] 吃特制美食"); }
@Override
public void walk() { System.out.println("[cooker] 走进厨房"); }
}
上述代码中, Cooker, Teacher, Programmer类都已经结构稳定, 运行得很好. 此时, 如果我想增加一个work方法, 但又不想在原来的类中做大幅的改动, 此时, 采用访问者模式是最保险的.
我需要做如下改动:
新增一个访问者接口, 并依据接口创建work操作实现类, 分别实现各个类特定的work功能.
java
interface Visitor {
void visit(Teacher teacher);
void visit(Programmer programmer);
void visit(Cooker cooker);
}
class WorkVisitor implements Visitor {
@Override
public void visit(Teacher teacher) {
System.out.println("[teacher] 授课工作中");
}
@Override
public void visit(Programmer programmer) {
System.out.println("[programmer] 编写代码中");
}
@Override
public void visit(Cooker cooker) {
System.out.println("[cooker] 正在炒菜");
}
}
- 在原有接口中增加accept方法, 利用多态机制, 帮助visitor中找回对象的真实原型
java
interface Human {
// ...
void accept(Visitor visitor);
}
class Teacher implements Human {
// ...
@Override
public void accept(Visitor visitor) {
visitor.visit(this); // 把自己交给访问者处理
}
}
class Programmer implements Human {
// ...
@Override
public void accept(Visitor visitor) {
visitor.visit(this); // 把自己交给访问者处理
}
}
class Cooker implements Human {
// ...
@Override
public void accept(Visitor visitor) {
visitor.visit(this); // 把自己交给访问者处理
}
}
最终调用
java
WorkVisitor workVisitor = new WorkVisitor();
for(Human human : humanList) {
//...
human.accept(workVisitor);
}
完整的访问者模式代码示例如下:
java
import java.util.List;
import java.util.ArrayList;
public class VisitorPattern {
public static void main(String[] args) {
Teacher teacher = new Teacher();
Programmer programmer = new Programmer();
Cooker cooker = new Cooker();
List<Human> humanList = new ArrayList<>();
humanList.add(teacher);
humanList.add(programmer);
humanList.add(cooker);
WorkVisitor workVisitor = new WorkVisitor();
for(Human human : humanList) {
human.eat();
human.walk();
human.accept(workVisitor);
}
}
}
interface Human {
void eat();
void walk();
void accept(Visitor visitor);
}
class Teacher implements Human {
@Override
public void eat() {
System.out.println("[teacher] 吃教师员工餐");
}
@Override
public void walk() {
System.out.println("[teacher] 走入教室");
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this); // 把自己交给访问者处理
}
}
class Programmer implements Human {
@Override
public void eat() {
System.out.println("[programmer] 吃外卖");
}
@Override
public void walk() {
System.out.println("[Programmer] 走向工位");
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this); // 把自己交给访问者处理
}
}
class Cooker implements Human {
@Override
public void eat() { System.out.println("[cooker] 吃特制美食"); }
@Override
public void walk() { System.out.println("[cooker] 走进厨房"); }
@Override
public void accept(Visitor visitor) {
visitor.visit(this); // 把自己交给访问者处理
}
}
interface Visitor {
void visit(Teacher teacher);
void visit(Programmer programmer);
void visit(Cooker cooker);
}
class WorkVisitor implements Visitor {
@Override
public void visit(Teacher teacher) {
System.out.println("[teacher] 授课工作中");
}
@Override
public void visit(Programmer programmer) {
System.out.println("[programmer] 编写代码中");
}
@Override
public void visit(Cooker cooker) {
System.out.println("[cooker] 正在炒菜");
}
}
输出
[teacher] 吃教师员工餐
[teacher] 走入教室
[teacher] 授课工作中
[programmer] 吃外卖
[Programmer] 走向工位
[programmer] 编写代码中
[cooker] 吃特制美食
[cooker] 走进厨房
[cooker] 正在炒菜
访问者模式的巧妙之处在于基于多态的双分派
如果在实例化后不是通过for循环统一调用的, 而是在编译阶段就知道了实例的类型, 那就是基于多态的单分派机制
java
teacher.eat();
teacher.walk();
programmer.eat();
programmer.walk();
cooker.eat();
cooker.walk();
WorkVisitor workVisitor = new WorkVisitor();
workVisitor.visit(teacher);
如上, 这样访问时, interface Human 中不需要增加 void accept(Visitor visitor);方法
但是增加了 accept 之后, 就实现了第二次分派, 能够实现如下调用
java
WorkVisitor workVisitor = new WorkVisitor();
for(Human human : humanList) {
human.eat();
human.walk();
// 此处的human对象究竟是基于哪个类实例的, 未知, 以Teacher类为例
// 第一次分派, human指向Teacher类的对象, 进入Teacher类的accept内部
// 第二次分派在accept方法中, visitor.visit(this), 在编译时, this已经确定是Teacher, 帮助访问者找到visitor对象的真实类型.
human.accept(workVisitor); // 双分派,
}