目录
定义
结构
案例
优点
缺点
使用场景
扩展
分派
案例实现须知
动态分派
静态分派
双分派
封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
访问者模式包含以下主要角色:
抽象访问类
//(参数为要访问的元素,且有几个元素就有几个方法)
public interface Person {
//可以将feed方法名改为visit主要就是为了访问元素,具体实现可以在子类中实现
void feed(Dog dog);
void feed(Cat cat);
}
抽象元素类
//(只定义一个被访问的方法即可)
public interface Animal {
void accept(Person person);
}
具体元素类
public class Dog implements Animal {
@Override
public void accept(Person person) {
person.feed(this);
System.out.println("修勾接受了食物");
}
}
public class Cat implements Animal{
@Override
public void accept(Person person) {
person.feed(this);
System.out.println("修猫接受了食物");
}
}
具体访问类
public class Owner implements Person{
@Override
public void feed(Dog dog) {
System.out.println("主人投喂食物");
}
@Override
public void feed(Cat cat) {
System.out.println("主人投喂食物");
}
}
public class Someone implements Person{
@Override
public void feed(Dog dog) {
System.out.println("陌生人投喂了食物");
}
@Override
public void feed(Cat cat) {
System.out.println("陌生人投喂了食物");
}
}
对象结构角色
public class Home {
private List animals = new ArrayList<>();
public void action(Person person){
for (Animal animal : animals) {
animal.accept(person);
}
}
public void addAnimal(Animal animal){
animals.add(animal);
}
}
测试
public class Client {
public static void main(String[] args) {
Home home = new Home();
home.addAnimal(new Dog());
home.addAnimal(new Cat());
home.action(new Owner());
}
}
主人投喂食物
修勾接受了食物
主人投喂食物
修猫接受了食物
通过对象结构角色来访问具体元素。而访问者通过参数来控制。访问具体元素后调用访问者对应的方法。实现了访问者通过访问不同元素有不同的行为。
访问者模式用到了一种双分派的技术。
变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如 Map map = new HashMap() ,map变量的静态类型是 Map ,实际类型是HashMap 。根据对象的类型而对方法进行的选择,就是分派,分派又分为两种,即静态分派和动态分派。
静态分派:发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。
动态分派:发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。
编译看左,运行看右。
即Map map = new HashMap(),map的真实类型在编译时期是不知道的,在运行后才会知道map的真实类型为HashMap。
对于重写的方法,通过子类对象访问到子类中的方法。
对于重载的方法,分派是根据静态类型进行的,参数类型在编译时期就已经确定了。
public class Animal{
void print(){
System.out.println("animal");
}
}
public class Dog extends Animal{
void print(){
System.out.println("dog");
}
}
public class Cat extends Animal{
@Override
void print() {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal animal = new Animal();
Animal dog = new Dog();
Animal cat = new Cat();
animal.print();
dog.print();
cat.print();
}
}
animal
dog
cat
由于子类重写了print()方法,因此在运行时动态调用的是真实对象中的print()方法。即多态的实现。
public class Animal{
}
public class Dog extends Animal {
}
public class Cat extends Animal {
}
public class Execute {
void print(Animal animal){
System.out.println("animal");
}
void print(Dog dog){
System.out.println("dog");
}
void print(Cat cat){
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal animal = new Animal();
Animal dog = new Dog();
Animal cat = new Cat();
Execute execute = new Execute();
execute.print(animal);
execute.print(dog);
execute.print(cat);
}
}
animal
animal
animal
对应实现须知中的重载方法部分,对于execute.print()方法,参数dog与cat在编译时期就已经确定了他们的类型是静态类型Animal,因此在运行时并不会通过new Dog()与new Cat()在对dog与cat动态分派
所谓双分派技术就是因为重载在选择一个方法的时候,不仅仅要根据消息接收者(即上例中的execute运行时的真实类型)的运行时区别,还要根据参数(即参数在运行时的真实类型)的运行时区别。
public class Animal{
void accept(Execute execute){
execute.print(this);
}
}
public class Dog extends Animal {
@Override
void accept(Execute execute) {
execute.print(this);
}
}
public class Cat extends Animal {
@Override
void accept(Execute execute) {
execute.print(this);
}
}
public class Execute {
void print(Animal animal){
System.out.println("animal");
}
void print(Dog dog){
System.out.println("dog");
}
void print(Cat cat){
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Execute execute = new Execute();
Animal animal = new Animal();
Animal dog = new Dog();
Animal cat = new Cat();
animal.accept(execute);
dog.accept(execute);
cat.accept(execute);
}
}
animal
dog
cat
重载与重写相结合,先进行子类中重写的方法,这里执行第一次分派是动态分派,子类实现方法中将自身作为参数去执行重载方法,完成第二次分派
双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。