- 封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新操作。
结构
- 抽象访问者(Visitor)角色:定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
- 具体访问者(ConcreteVisitor)角色:给出对每一个元素类访问时所产生的具体行为。
- 抽象元素(Element)角色:定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
- 具体元素(ConcreteElement)角色: 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
- 对象结构(Object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。
实例
// 抽象元素角色(被访问者)
// Animal.java
public interface Animal {
// 接受访问者访问的功能
void accept(Person person);
}
// 具体元素角色
// Cat.java
public class Cat implements Animal{
@Override
public void accept(Person person) {
person.feed(this); // 访问者给猫喂食
System.out.println("猫吃饱了");
}
}
// 具体元素角色
// Dog.java
public class Dogimplements Animal{
@Override
public void accept(Person person) {
person.feed(this); // 访问者给狗喂食
System.out.println("狗吃饱了");
}
}
// 抽象访问者角色
// Person.java
public interface Person {
// 喂食狗
void feed(Cat cat);
// 喂食猫
void feed(Dog dog);
}
// 具体访问者角色
// Owner.java
public class Owner implements Person{
@Override
public void feed(Cat cat) {
System.out.println("主人喂食猫");
}
@Override
public void feed(Dog dog) {
System.out.println("主人喂食狗");
}
}
// 具体访问者角色
// Someone.java
public class Someone implements Person{
@Override
public void feed(Cat cat) {
System.out.println("客人喂食猫");
}
@Override
public void feed(Dog dog) {
System.out.println("客人喂食狗");
}
}
// 对象结构角色
// Home.java
public class Home {
// 声明一个结合对象,用来存储元素对象
private List<Animal> nodeList = new ArrayList<>();
// 添加元素功能
public void add(Animal animal){
nodeList.add(animal);
}
public void action(Person person){
// 遍历结合,获取每一个元素,让访问者访问每一个元素
for(Animal animal:nodeList){
animal.accept(person);
}
}
}
// Client.java
public class Client {
public static void main(String[] args) {
// 创建home对象
Home home = new Home();
// 添加元素到Home对象中
home.add(new Dog());
home.add(new Cat());
// 创建主人对象
Owner owner = new Owner();
// 让主人喂食宠物
home.action(owner);
}
}
Java中的分派
- 变量被声明时的类型叫做变量的静态类型;
- 变量所引用的对象的类型叫做对象的真实类型,也就是实际类型。
- 例如
Map map = new HashMap()
中,map变量的静态类型是Map,实际类型是HashMap;
- 根据对象的类型而对方法进行的选择就是分派(Dispatch),分派又分为两种:静态分派和动态分派。
- 静态分派(Static Dispatch)发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派;
- 动态分派(Dynamic Dispatch)发生在运行时期,动态分派动态地置换掉了某个方法。Java通过对方法的重写支持动态分派。
动态分派
public class Animal {
public void execute() {
System.out.println("Animal");
}
}
public class Dog extends Animal {
@Override
public void execute() {
System.out.println("dog");
}
}
public class Cat extends Animal {
@Override
public void execute() {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Dog();
a.execute();
Animal a1 = new Cat();
a1.execute();
}
}
静态分派
public class Animal {
}
public class Dog extends Animal {
}
public class Cat extends Animal {
}
public class Execute {
public void execute(Animal a) {
System.out.println("Animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Animal();
Animal a1 = new Dog();
Animal a2 = new Cat();
Execute exe = new Execute();
exe.execute(a);
exe.execute(a1);
exe.execute(a2);
}
}
/*
执行结果:
animal
animal
animal
*/
- 重载方法的分派是根据静态变量类型进行的,这个分派过程在编译阶段就已经完成。
双分派技术
public class Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Dog extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Cat extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Execute {
public void execute(Animal a) {
System.out.println("animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Animal();
Animal d = new Dog();
Animal c = new Cat();
Execute exe = new Execute();
a.accept(exe);
d.accept(exe);
c.accept(exe);
}
}
- 在该段代码的执行逻辑中,发生了两次分派行为。
- 首先在,a、d、c三个对象在调用accept()的时候,根据对于方法的重写,发生一次动态分派,也就是执行了对象的实际类型中的方法;
- 其中,在每个方法的内部,其调用了execute()方法,在execute()方法中,根据传递的this类型的不同,会静态的分派到重载的方法中,发生了第二次分派。
- 双分派的意图就是在静态的重载前面加上了动态的重写,从而实现了动态的重载。