继续整理记录这段时间来的收获,详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用!
6.10 访问者模式
6.10.1 定义
封装一些作用域某种数据结构中的各元素的操作,可以在不改变此数据结构的前提下定义作用于这些元素的新操作
6.10.2 结构
- 抽象访问者角色(Visitor):定义了对每个元素访问的行为,参数是可以访问的元素,理论上方法个数与元素类个数(Element的实现类个数)一样,所以访问者模式要求元素类个数不能改变
- 具体访问者角色(Concrete Visitor):给出对每个元素类访问所产生的的具体行为
- 抽象元素角色(Element):定义了一个接受访问者类的方法,意义是指每一个元素都要可以被访问者访问
- 具体元素角色(Concrete Element):提供接受访问方法的具体实现,通常情况下使用访问者提供的访问该元素类方法
- 对象结构角色(Object Structure):定义当中所提到的对象结构,对象结构是一个抽象表述,具体可理解为一个具有容器性质或符合对象特性的类,会含有一组元素,并且可以迭代这些元素,供访问者访问
6.10.3 案例(喂宠物)
- 抽象访问者
public interface Person {
public void feed(Animal animal);
}
- 抽象元素
public interface Animal {
public void accept(Person person);
}
- 具体元素
public class Cat implements Animal{
@Override
public void accept(Person person) {
System.out.println("好吃,喵喵~~");
}
}
public class Dog implements Animal{
@Override
public void accept(Person person) {
System.out.println("好吃,汪汪~~");
}
}
- 具体访问者
public class Owner implements Person{
@Override
public void feed(Animal animal) {
animal.accept(this);
}
}
public class Someone implements Person{
@Override
public void feed(Animal animal) {
animal.accept(this);
}
}
- 对象结构
public class Home {
private List<Animal> list = new ArrayList<Animal>();
// 添加宠物
public void addAnimal(Animal animal)
{
list.add(animal);
}
// 喂养宠物
public void action(Person person){
for (Animal animal : list) {
person.feed(animal);
}
}
}
- 测试
public static void main(String[] args) {
Home home = new Home();
// 创建宠物对象
// 添加宠物
home.addAnimal(new Dog());
home.addAnimal(new Cat());
// 喂养着
Owner owner = new Owner();
Someone someone = new Someone();
home.action(owner);
home.action(someone);
}
-
结果
-
类图
6.10.4 优缺点
6.10.4.1 优点
- 扩展性好:在不修改对象结构中元素的情况下,为对象结构中元素添加新功能
- 复用性好:通过访问者来定义整个对象结构通用的功能,从而提高复用程度
- 分类无关行为:通过此模式分离无关行为,将相关行为封装在一起,构成一个访问者,故每个访问者功能单一
6.10.4.2 缺点
- 对象结构变化很困难:每增加一个新的元素类,都在每个具体访问者类中增加相应的具体操作,违背了“开闭原则”
- 违反依赖倒置原则:此模式依赖具体类,而没有依赖抽象类
6.10.5 使用场景
- 对象结构相对稳定,但其操作算法经常变化的程序
- 对象结构中对象需要提供多种不同且不相关的操作,且要避免这些操作变化影响对象的结果
6.10.6 扩展
访问者模式使用了一种双分派技术
6.10.6.1 分派
- 变量静态类型:变量被声明时的类型
- 明显类型:也称静态类型
- 变量实际类型:变量所引用的对象的真实类型
- 比如:
Map map = new HashMap()
,map静态类型是Map,实际类型是HashMap - 分派:根据对象的类型而对方法的选择,分为两类:
- 静态分派(Static Dispatch):发生在编译时期,根据静态类型信息选择,方法重载就属于静态分派
- 动态分派(Dynamic Dispatch):发生在运行时期,动态地置换方法,Java通过重写支持动态分派
6.10.6.2 动态分派
代码如下:
public class Animal {
public void execute(){
System.out.println("Animal");
}
}
public class Cat extends Animal{
public void execute(){
System.out.println("Cat");
}
}
public class Dog extends Animal{
public void execute(){
System.out.println("Dog");
}
}
public static void main(String[] args) {
Animal a = new Dog();
Animal a1 = new Cat();
a.execute();
a1.execute();
}
结果是Dog和Cat输出,运行执行的是子类方法,多态
Java编译器在编译时期并不晓得哪些代码会被执行,因为编译器仅仅知道对象静态类型,不知对象真实类型,方法调用则是根据真实类型来执行,不是静态类型
6.10.6.3 静态分派
代码如下:
public class Animal {
}
public class Cat extends Animal {
}
public class Dog extends Animal {
}
public class Execute {
public void execute(Animal a){
System.out.println("Animal");
}
public void execute(Dog dog){
System.out.println("Dog");
}
public void execute(Cat cat){
System.out.println("Cat");
}
}
public static void main(String[] args) {
Animal a = new Animal();
Animal a1 = new Dog();
Animal a2 = new Cat();
Execute execute = new Execute();
execute.execute(a);
execute.execute(a1);
execute.execute(a2);
}
结果全是Animal,静态分派,在编译时期完成
6.10.6.4 双分派
即在选择方法的时候,不仅根据消息接收者运行时区分,还要根据参数类型运行区分,代码如下:
public class Animal {
public void execute(Execute e){
e.execute(this);
}
}
public class Cat extends Animal {
public void execute(Execute e){
e.execute(this);
}
}
public class Dog extends Animal {
public void execute(Execute e){
e.execute(this);
}
}
public class Execute {
public void execute(Animal a){
System.out.println("Animal");
}
public void execute(Dog dog){
System.out.println("Dog");
}
public void execute(Cat cat){
System.out.println("Cat");
}
}
public static void main(String[] args) {
Animal a = new Animal();
Animal a1 = new Dog();
Animal a2 = new Cat();
Execute execute = new Execute();
a.execute(execute);
a1.execute(execute);
a2.execute(execute);
}
结果为Animal、Dog、Cat。
上述测试代码将Execute对象作为参数传递给Animal类型的变量调用方法,这是第一次分派,是方法重写,动态分派,即执行的是实际类型中方法,同时将自己this作为参数传递进去,完成第二次分派,其中Execute类有多个重载方法,传递的是this,也是具体的实际类型
本质:
动态绑定,即在重载方法委派前加上继承体系中覆盖环节