定义
访问者模式是一种从操作的对象结构中分离算法的方式。 它可以在不改变数据结构的前提下定义作用与这些元素的新操作。它遵循开闭原则。
Represent an operation to be performed on elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
visitor: n. 访问者,参观者;视察者.
涉及角色
- Visitor 抽象访问者角色,为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色,这样访问者就可以通过该元素角色的特定接口直接访问它。
- ConcreteVisitor.具体访问者角色,实现Visitor声明的接口。
- Element 定义一个接受访问操作(accept()),它以一个访问者(Visitor)作为参数。
- ConcreteElement 具体元素,实现了抽象元素(Element)所定义的接受操作接口。
- ObjectStructure 结构对象角色,这是使用访问者模式必备的角色。它具备以下特性:能枚举它的元素;可以提供一个高层接口以允许访问者访问它的元素;如有需要,可以设计成一个复合对象或者一个聚集(如一个列表或无序集合)
通俗理解
- 我作为一个访客(Visitor)到朋友家(Element)拜访,朋友之间喝喝酒,聊聊天,再互相吹捧。聊天的时候,朋友告诉我他今年的表现(doSomthing),然后我就做(visit-self-method)一些对这件事的评价。
- 老板作为视察者,查阅(访问)手下员工的工作业绩。老板是Visitor的抽象实现,员工是Element的抽象实现。对象结构(Object Structure)为员工的业绩等信息
- 家里有一台电脑,电脑出现了一点问题,那么我作为访问者,想去了解电脑的那个部分出了问题。我Visitor,电脑的各个部分(Element),查看有没有坏(visit method)
应该有很多类似的比喻,在开发的过程中多去思考,做什么事情都要思考。
实现细节
- 定义一个表示Element的接口
- 实现Element接口。创建Element的实体类ConcreteElement
- 创建一个表示访问者Visitor的接口
- 实现Visitor的接口,创建Visitor实体类ConcreteVisitor,(有时候会有多个访问者)
- 使用Visitor实体类来访问Element。
特性
优点
- 符合单一职责原则
- 元素类可以通过接受不同的访问者来实现对不同操作的扩展。
缺点
- 具体元素对访问者公布细节,违背了迪米特法则。
- 违背了依赖倒置原则,访问者依赖的是具体元素,而不是抽象元素。
适用场景
- 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
注意事项:访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。
案例
案例一
类图我们去检查汽车的各个部分是否能正常打印,使用Visitor根据不同的汽车部分来分发动作。 而不是在汽车的各个部分来打印。
interface CarElement {
void accept(CarElementVisitor visitor);
}
interface CarElementVisitor {
void visit(Body body);
void visit(Car car);
void visit(Engine engine);
void visit(Wheel wheel);
}
class Car implements CarElement {
CarElement[] elements;
public Car() {
this.elements = new CarElement[] {
new Wheel("front left"), new Wheel("front right"),
new Wheel("back left"), new Wheel("back right"),
new Body(), new Engine()
};
}
public void accept(final CarElementVisitor visitor) {
for (CarElement elem : elements) {
elem.accept(visitor);
}
visitor.visit(this);
}
}
class Body implements CarElement {
public void accept(final CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Engine implements CarElement {
public void accept(final CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Wheel implements CarElement {
private String name;
public Wheel(final String name) {
this.name = name;
}
public String getName() {
return name;
}
public void accept(final CarElementVisitor visitor) {
/*
* accept(CarElementVisitor) in Wheel implements
* accept(CarElementVisitor) in CarElement, so the call
* to accept is bound at run time. This can be considered
* the *first* dispatch. However, the decision to call
* visit(Wheel) (as opposed to visit(Engine) etc.) can be
* made during compile time since 'this' is known at compile
* time to be a Wheel. Moreover, each implementation of
* CarElementVisitor implements the visit(Wheel), which is
* another decision that is made at run time. This can be
* considered the *second* dispatch.
*/
visitor.visit(this);
}
}
class CarElementDoVisitor implements CarElementVisitor {
public void visit(final Body body) {
System.out.println("Moving my body");
}
public void visit(final Car car) {
System.out.println("Starting my car");
}
public void visit(final Wheel wheel) {
System.out.println("Kicking my " + wheel.getName() + " wheel");
}
public void visit(final Engine engine) {
System.out.println("Starting my engine");
}
}
class CarElementPrintVisitor implements CarElementVisitor {
public void visit(final Body body) {
System.out.println("Visiting body");
}
public void visit(final Car car) {
System.out.println("Visiting car");
}
public void visit(final Engine engine) {
System.out.println("Visiting engine");
}
public void visit(final Wheel wheel) {
System.out.println("Visiting " + wheel.getName() + " wheel");
}
}
public class VisitorDemo {
public static void main(final String[] args) {
final Car car = new Car();
car.accept(new CarElementPrintVisitor());
car.accept(new CarElementDoVisitor());
}
}
/* 输出内容
Visiting front left wheel
Visiting front right wheel
Visiting back left wheel
Visiting back right wheel
Visiting body
Visiting engine
Visiting car
Kicking my front left wheel
Kicking my front right wheel
Kicking my back left wheel
Kicking my back right wheel
Moving my body
Starting my engine
Starting my car
*/
案例二
类图本质上和案例一没什么差别
// ComputerPart.java
public interface ComputerPart {
public void accept(ComputerPartVisitor computerPartVisitor);
}
// Keyboard.java
public class Keyboard implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
// Monitor.java
public class Monitor implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
// Mouse.java
public class Mouse implements ComputerPart {
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
computerPartVisitor.visit(this);
}
}
// Computer.java
public class Computer implements ComputerPart {
ComputerPart[] parts;
public Computer(){
parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};
}
@Override
public void accept(ComputerPartVisitor computerPartVisitor) {
for (int i = 0; i < parts.length; i++) {
parts[i].accept(computerPartVisitor);
}
computerPartVisitor.visit(this);
}
}
// ComputerPartVisitor
public interface ComputerPartVisitor {
public void visit(Computer computer);
public void visit(Mouse mouse);
public void visit(Keyboard keyboard);
public void visit(Monitor monitor);
}
// ComputerPartDisplayVisitor.java
public class ComputerPartDisplayVisitor implements ComputerPartVisitor {
@Override
public void visit(Computer computer) {
System.out.println("Displaying Computer.");
}
@Override
public void visit(Mouse mouse) {
System.out.println("Displaying Mouse.");
}
@Override
public void visit(Keyboard keyboard) {
System.out.println("Displaying Keyboard.");
}
@Override
public void visit(Monitor monitor) {
System.out.println("Displaying Monitor.");
}
}
// demo
public class VisitorPatternDemo {
public static void main(String[] args) {
ComputerPart computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
}
}
/* 输出
Displaying Mouse.
Displaying Keyboard.
Displaying Monitor.
Displaying Computer.
*/
小结
主要记录了在学习设计模式时的一些资料,对资料进行了整理。想要深入理解设计模式,还要多读优秀的代码,在开发的时候多去思考相关的应用场景。
网友评论