定義
訪問者模式是一種從操作的對(duì)象結(jié)構(gòu)中分離算法的方式。 它可以在不改變數(shù)據(jù)結(jié)構(gòu)的前提下定義作用與這些元素的新操作揭鳞。它遵循開閉原則炕贵。
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 抽象訪問者角色称开,為該對(duì)象結(jié)構(gòu)中具體元素角色聲明一個(gè)訪問操作接口。該操作接口的名字和參數(shù)標(biāo)識(shí)了發(fā)送訪問請(qǐng)求給具體訪問者的具體元素角色,這樣訪問者就可以通過該元素角色的特定接口直接訪問它鳖轰。
- ConcreteVisitor.具體訪問者角色清酥,實(shí)現(xiàn)Visitor聲明的接口。
- Element 定義一個(gè)接受訪問操作(accept())蕴侣,它以一個(gè)訪問者(Visitor)作為參數(shù)焰轻。
- ConcreteElement 具體元素,實(shí)現(xiàn)了抽象元素(Element)所定義的接受操作接口昆雀。
- ObjectStructure 結(jié)構(gòu)對(duì)象角色辱志,這是使用訪問者模式必備的角色。它具備以下特性:能枚舉它的元素忆肾;可以提供一個(gè)高層接口以允許訪問者訪問它的元素荸频;如有需要,可以設(shè)計(jì)成一個(gè)復(fù)合對(duì)象或者一個(gè)聚集(如一個(gè)列表或無序集合)
通俗理解
- 我作為一個(gè)訪客(Visitor)到朋友家(Element)拜訪客冈,朋友之間喝喝酒旭从,聊聊天,再互相吹捧场仲。聊天的時(shí)候和悦,朋友告訴我他今年的表現(xiàn)(doSomthing),然后我就做(visit-self-method)一些對(duì)這件事的評(píng)價(jià)渠缕。
- 老板作為視察者鸽素,查閱(訪問)手下員工的工作業(yè)績。老板是Visitor的抽象實(shí)現(xiàn)亦鳞,員工是Element的抽象實(shí)現(xiàn)馍忽。對(duì)象結(jié)構(gòu)(Object Structure)為員工的業(yè)績等信息
- 家里有一臺(tái)電腦,電腦出現(xiàn)了一點(diǎn)問題燕差,那么我作為訪問者遭笋,想去了解電腦的那個(gè)部分出了問題。我Visitor徒探,電腦的各個(gè)部分(Element)瓦呼,查看有沒有壞(visit method)
應(yīng)該有很多類似的比喻,在開發(fā)的過程中多去思考测暗,做什么事情都要思考央串。
實(shí)現(xiàn)細(xì)節(jié)
- 定義一個(gè)表示Element的接口
- 實(shí)現(xiàn)Element接口。創(chuàng)建Element的實(shí)體類ConcreteElement
- 創(chuàng)建一個(gè)表示訪問者Visitor的接口
- 實(shí)現(xiàn)Visitor的接口碗啄,創(chuàng)建Visitor實(shí)體類ConcreteVisitor质和,(有時(shí)候會(huì)有多個(gè)訪問者)
- 使用Visitor實(shí)體類來訪問Element。
特性
優(yōu)點(diǎn)
- 符合單一職責(zé)原則
- 元素類可以通過接受不同的訪問者來實(shí)現(xiàn)對(duì)不同操作的擴(kuò)展挫掏。
缺點(diǎn)
- 具體元素對(duì)訪問者公布細(xì)節(jié)侦另,違背了迪米特法則。
- 違背了依賴倒置原則,訪問者依賴的是具體元素褒傅,而不是抽象元素弃锐。
適用場(chǎng)景
- 對(duì)象結(jié)構(gòu)中對(duì)象對(duì)應(yīng)的類很少改變,但經(jīng)常需要在此對(duì)象結(jié)構(gòu)上定義新的操作殿托。
- 需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作霹菊,而需要避免讓這些操作"污染"這些對(duì)象的類,也不希望在增加新操作時(shí)修改這些類支竹。
注意事項(xiàng):訪問者可以對(duì)功能進(jìn)行統(tǒng)一旋廷,可以做報(bào)表、UI礼搁、攔截器與過濾器饶碘。
案例
案例一
我們?nèi)z查汽車的各個(gè)部分是否能正常打印,使用Visitor根據(jù)不同的汽車部分來分發(fā)動(dòng)作馒吴。 而不是在汽車的各個(gè)部分來打印扎运。
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());
}
}
/* 輸出內(nèi)容
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
*/
案例二
本質(zhì)上和案例一沒什么差別
// 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.
*/
小結(jié)
主要記錄了在學(xué)習(xí)設(shè)計(jì)模式時(shí)的一些資料,對(duì)資料進(jìn)行了整理饮戳。想要深入理解設(shè)計(jì)模式豪治,還要多讀優(yōu)秀的代碼,在開發(fā)的時(shí)候多去思考相關(guān)的應(yīng)用場(chǎng)景扯罐。