共计 5651 个字符,预计需要花费 15 分钟才能阅读完成。
写在后面
- 记录学习设计模式的笔记
- 进步对设计模式的灵活运用
学习地址
https://www.bilibili.com/vide…
https://www.bilibili.com/vide…
参考文章
http://c.biancheng.net/view/1…
我的项目源码
https://gitee.com/zhuang-kang/DesignPattern
19,访问者模式
19.1 访问者模式的定义和特点
访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不扭转数据结构的前提下能够增加作用于这些元素的新的操作,为数据结构中的每个元素提供多种拜访形式。它将对数据的操作与数据结构进行拆散,是行为类模式中最简单的一种模式。
访问者(Visitor)模式是一种对象行为型模式,其次要长处如下。
- 扩展性好。可能在不批改对象构造中的元素的状况下,为对象构造中的元素增加新的性能。
- 复用性好。能够通过访问者来定义整个对象构造通用的性能,从而进步零碎的复用水平。
- 灵活性好。访问者模式将数据结构与作用于构造上的操作解耦,使得操作汇合可绝对自在地演变而不影响零碎的数据结构。
- 合乎繁多职责准则。访问者模式把相干的行为封装在一起,形成一个访问者,使每一个访问者的性能都比拟繁多。
访问者(Visitor)模式的次要毛病如下。
- 减少新的元素类很艰难。在访问者模式中,每减少一个新的元素类,都要在每一个具体访问者类中减少相应的具体操作,这违反了“开闭准则”。
- 毁坏封装。访问者模式中具体元素对访问者颁布细节,这毁坏了对象的封装性。
- 违反了依赖倒置准则。访问者模式依赖了具体类,而没有依赖抽象类。
19.2 访问者模式的构造与实现
19.2.1 访问者模式的构造
- 形象访问者(Visitor)角色:定义一个拜访具体元素的接口,为每个具体元素类对应一个拜访操作 visit(),该操作中的参数类型标识了被拜访的具体元素。
- 具体访问者(ConcreteVisitor)角色:实现形象访问者角色中申明的各个拜访操作,确定访问者拜访一个元素时该做什么。
- 形象元素(Element)角色:申明一个蕴含承受操作 accept() 的接口,被承受的访问者对象作为 accept() 办法的参数。
- 具体元素(ConcreteElement)角色:实现形象元素角色提供的 accept() 操作,其办法体通常都是 visitor.visit(this),另外具体元素中可能还蕴含自身业务逻辑的相干操作。
- 对象构造(Object Structure)角色:是一个蕴含元素角色的容器,提供让访问者对象遍历容器中的所有元素的办法,通常由 List、Set、Map 等聚合类实现。
19.2.2 代码实现
当初养宠物的人特地多,咱们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,客人能够喂,其他人也能够喂食。
- 访问者角色:给宠物喂食的人
- 具体访问者角色:客人、其他人
- 形象元素角色:动物抽象类
- 具体元素角色:宠物狗、宠物猫
- 构造对象角色:主人家
Person
package com.zhuang.visitor;
/**
* @Classname Person
* @Description 形象访问者接口
* @Date 2021/3/27 16:48
* @Created by dell
*/
public interface Person {
// 喂宠物狗
void feed(Dog dog);
// 喂宠物猫
void feed(Cat cat);
}
Owner
package com.zhuang.visitor;
/**
* @Classname Owner
* @Description 具体访问者角色 主人类
* @Date 2021/3/27 16:49
* @Created by dell
*/
public class Owner implements Person {
@Override
public void feed(Dog dog) {System.out.println("客人喂食宠物狗...");
}
@Override
public void feed(Cat cat) {System.out.println("客人喂食宠物猫...");
}
}
Someone
package com.zhuang.visitor;
/**
* @Classname Someone
* @Description 具体访问者角色 其余人类
* @Date 2021/3/27 16:49
* @Created by dell
*/
public class Someone implements Person {
@Override
public void feed(Dog dog) {System.out.println("其他人喂食宠物狗...");
}
@Override
public void feed(Cat cat) {System.out.println("其他人喂食宠物猫...");
}
}
Animal
package com.zhuang.visitor;
/**
* @Classname Animal
* @Description 定义形象节点
* @Date 2021/3/27 16:50
* @Created by dell
*/
public interface Animal {void accept(Person person);
}
Dog
package com.zhuang.visitor;
/**
* @Classname Dog
* @Description 具体节点 实现 Animal 接口
* @Date 2021/3/27 16:48
* @Created by dell
*/
public class Dog implements Animal {
@Override
public void accept(Person person) {person.feed(this);
System.out.println("真香~,汪汪汪!!!");
}
}
Cat
package com.zhuang.visitor;
/**
* @Classname Cat
* @Description 用一句话形容类的作用
* @Date 2021/3/27 16:49
* @Created by dell
*/
public class Cat implements Animal {
@Override
public void accept(Person person) {person.feed(this);
System.out.println("真香~,喵喵喵!!!");
}
}
Home
package com.zhuang.visitor;
import java.util.ArrayList;
import java.util.List;
/**
* @Classname Home
* @Description 定义对象构造 客人的家
* @Date 2021/3/27 16:50
* @Created by dell
*/
public class Home {private List<Animal> nodeList = new ArrayList<Animal>();
// 增加办法
public void add(Animal animal) {nodeList.add(animal);
}
public void aciton(Person person) {for (Animal node : nodeList) {node.accept(person);
}
}
}
Client
package com.zhuang.visitor;
/**
* @Classname Client
* @Description 访问者模式 测试类
* @Date 2021/3/27 16:50
* @Created by dell
*/
public class Client {public static void main(String[] args) {Home home = new Home();
home.add(new Dog());
home.add(new Cat());
Owner owner = new Owner();
home.aciton(owner);
System.out.println("===========================");
Someone someone = new Someone();
home.aciton(someone);
}
}
19.3 扩大
访问者模式用到了一种双分派的技术。
1,分派:
变量被申明时的类型叫做变量的动态类型,有些人又把动态类型叫做显著类型;而变量所援用的对象的实在类型又叫做变量的理论类型。比方 Map map = new HashMap()
,map 变量的动态类型是 Map
,理论类型是 HashMap
。依据对象的类型而对办法进行的抉择,就是分派 (Dispatch),分派(Dispatch) 又分为两种,即动态分派和动静分派。
动态分派(Static Dispatch) 产生在编译期间,分派依据动态类型信息产生。动态分派对于咱们来说并不生疏,办法重载就是动态分派。
动静分派(Dynamic Dispatch) 产生在运行期间,动静分派动静地置换掉某个办法。Java 通过办法的重写反对动静分派。
2,动静分派:
通过办法的重写反对动静分派。
public class Animal {public void execute() {System.out.println("Animal");
}
}
public class Dog extends Animal {
@Override
public void execute() {System.out.println("我是狗...");
}
}
public class Cat extends Animal {
@Override
public void execute() {System.out.println("我是猫...");
}
}
public class Client {public static void main(String[] args) {Animal a = new Dog();
a.execute();
Animal a1 = new Cat();
a1.execute();}
}
下面代码的后果大家应该间接能够说进去,这不就是多态吗!运行执行的是子类中的办法。
Java 编译器在编译期间并不总是晓得哪些代码会被执行,因为编译器仅仅晓得对象的动态类型,而不晓得对象的实在类型;而办法的调用则是依据对象的实在类型,而不是动态类型。
3,动态分派:
通过办法重载反对动态分派。
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("我是狗...");
}
public void execute(Cat c) {System.out.println("我是猫...");
}
}
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);
}
}
运行后果:
这个后果可能出乎一些人的预料了,为什么呢?
重载办法的分派是依据动态类型进行的,这个分派过程在编译期间就实现了。
4,双分派:
所谓双分派技术就是在抉择一个办法的时候,不仅仅要依据音讯接收者(receiver)的运行时区别,还要依据参数的运行时区别。
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("我是狗...");
}
public void execute(Cat c) {System.out.println("我是猫...");
}
}
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);
}
}
在下面代码中,客户端将 Execute 对象做为参数传递给 Animal 类型的变量调用的办法,这里实现第一次分派,这里是办法重写,所以是动静分派,也就是执行理论类型中的办法,同时也 将本人 this 作为参数传递进去,这里就实现了第二次分派
,这里的 Execute 类中有多个重载的办法,而传递进行的是 this,就是具体的理论类型的对象。
说到这里,咱们曾经明确双分派是怎么回事了,然而它有什么成果呢?就是能够实现办法的动静绑定,咱们能够对下面的程序进行批改。
运行后果如下:
双分派实现动静绑定的实质,就是在重载办法委派的后面加上了继承体系中笼罩的环节,因为笼罩是动静的,所以重载就是动静的了。
写在最初
- 如果我的文章对你有用,请给我点个👍,感激你😊!
- 有问题,欢送在评论区指出!💪