定义
访问者(Visitor)模式将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离。
访问者模式属于对象行为型模式,是行为型模式中最复杂的一种模式。
要点
优点:
- 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
- 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
- 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
- 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
缺点:
- 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
- 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
- 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
主要角色:
抽象访问者(Visitor):定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
具体访问者(ConcreteVisitor):实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
抽象元素(Element):声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
具体元素(ConcreteElement):实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
对象结构(Object Structure):是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。
场景
Component
/**
* 电脑组件,抽象元素
*/
public interface Component {
void accept(Visitor visitor);
}
CPU
/**
* CPU
*/
public class CPU implements Component {
private String brandName;
public CPU(String brandName) {
this.brandName = brandName;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getBrandName() {
return brandName;
}
}
GPU
/**
* GPU
*/
public class GPU implements Component {
private String brandName;
public GPU(String brandName) {
this.brandName = brandName;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getBrandName() {
return brandName;
}
}
Visitor
/**
* 抽象访问者
*/
public interface Visitor {
void visit(HardDisk hardDisk);
void visit(CPU cpu);
void visit(GPU gpu);
void visit(Memory memory);
}
ComponentVisitor
/**
* 电脑组件访问者
*/
public class ComponentVisitor implements Visitor {
@Override
public void visit(HardDisk hardDisk) {
System.out.println(String.format("您电脑的硬盘:%s", hardDisk.getBrandName()));
}
@Override
public void visit(CPU cpu) {
System.out.println(String.format("您电脑的CPU:%s", cpu.getBrandName()));
}
@Override
public void visit(GPU gpu) {
System.out.println(String.format("您电脑的GPU:%s", gpu.getBrandName()));
}
@Override
public void visit(Memory memory) {
System.out.println(String.format("您电脑的缓存:%s", memory.getBrandName()));
}
}
Computer
/**
* 计算机,相当于对象结构
*/
public class Computer {
private String brandName;
private List<Component> computerComponents = new ArrayList<>();
public Computer(String brandName) {
this.brandName = brandName;
}
public void addComponent(Component e) {
computerComponents.add(e);
}
public void removeComponent(Component e) {
computerComponents.remove(e);
}
public void accept(Visitor visitor) {
System.out.println(String.format("您的电脑品牌:%s", brandName));
for (Component e : computerComponents) {
e.accept(visitor);
}
}
}
Client
public class Client {
public static void main(String[] args) {
Computer macBookPro = new Computer("MacBook Pro 13' 2019");
macBookPro.addComponent(new CPU("2.6GHz 6核第九代 Intel Core i7"));
macBookPro.addComponent(new GPU("AMD Radeon Pro 5300M"));
macBookPro.addComponent(new HardDisk("512GB基于PCIe的SSD"));
macBookPro.addComponent(new Memory("16GB 2133MHz LPDDR3内存"));
macBookPro.accept(new ComponentVisitor());
}
}
------------------输出------------------
您的电脑品牌:MacBook Pro 13' 2019
您电脑的CPU:2.6GHz 6核第九代 Intel Core i7
您电脑的GPU:AMD Radeon Pro 5300M
您电脑的硬盘:512GB基于PCIe的SSD
您电脑的缓存:16GB 2133MHz LPDDR3内存
源码
总结
此模式的优点就是,如果操作的逻辑改变,我们只需要改变访问者的实现就够了,而不用去修改其他所有的元素类。访问者模式使我们更加容易的增加访问操作,但增加元素比较困难,需要我们修改抽象访问类和所有的具体访问类。
适用场景:
- 对象结构相对稳定,但其操作算法经常变化的程序。
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
- 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。