🔥 核心
访问者模式将数据结构与数据操作分离。
被访问者拥有一个接待访问者的方法,该方法实现调用主体的反转。
🙁 问题场景
你的朋友是一名资深的保险推销员。
迫于业绩的压力,他决定拜访街区中的每栋楼,尝试向他们推销保险。
他的业务能力非常优秀,可以根据不同的客户推销不同的保险产品。如果是居民楼,他会推销医疗保险;如果是银行,他会推销失窃保险;如果是餐厅,他会推销火灾意外险…
现在,你想为这个行为建模。
🙂 解决方案
这是个典型的适合访问者模式的场景——推销员是访问者,各种建筑是被访问者。
访问者具有一些以被访问者为参数的方法,这些方法的方法名是相同的,利用参数类型来重载。推销员具有这样的多个 visit(...)
方法,参数为各种建筑,方法体为不同的推销策略。
被访者至少具有一个接待访问者的 accept(...)
方法,这个方法实现了调用主体的反转,是本模式的关键。如何实现呢?accept()方法的调用者显然是 被访问者
,它的参数是 访问者
;在方法体中,转而将 访问者
作为调用者,将 被访问者
自身作为了参数。
只看文字可能难以理解,所以我推荐你瞧一瞧下面的有趣例子及其代码实现 >_<。
🌈 有趣的例子
森林里来了一个不速之客——他或许是个来自草原的朋友,谁知道呢?
这位 不速之客(AnimalVisitor)
拜访了森林里的许多小动物,小动物们(小青蛙(Frog) 、小鸭子(Duck) 、小乌鸦(Crow) 、小鼹鼠(Mole))
也热情的接待了他。
小动物们的家(接口)
interface Animal {
void accept(AnimalVisitor animalVisitor);
}
小青蛙的家
class Frog implements Animal {
@Override
public void accept(AnimalVisitor animalVisitor) {
animalVisitor.visit(this);
}
}
小鸭子的家
class Duck implements Animal {
@Override
public void accept(AnimalVisitor animalVisitor) {
animalVisitor.visit(this);
}
}
小乌鸦的家
class Crow implements Animal {
@Override
public void accept(AnimalVisitor animalVisitor) {
animalVisitor.visit(this);
}
}
小鼹鼠的家
class Mole implements Animal {
@Override
public void accept(AnimalVisitor animalVisitor) {
animalVisitor.visit(this);
}
}
森林的访问者
class AnimalVisitor {
void visit(Frog frog) {
System.out.println("我来拜访你了,小青蛙 >_< !!!");
}
void visit(Duck duck) {
System.out.println("我来拜访你了,小鸭子 >_< !!!");
}
void visit(Crow crow) {
System.out.println("我来拜访你了,小乌鸦 >_< !!!");
}
void visit(Mole mole) {
System.out.println("我来拜访你了,小鼹鼠 >_< !!!");
}
}
public class VisitorPatternDemo {
public static void main(String[] args) {
// 森林里来一个不速之客
AnimalVisitor animalVisitor = new AnimalVisitor();
// 小青蛙接待了ta
Frog frog = new Frog();
frog.accept(animalVisitor);
// 小鸭子接待了ta
Duck duck = new Duck();
duck.accept(animalVisitor);
// 小乌鸦接待了ta
Crow crow = new Crow();
crow.accept(animalVisitor);
// 小鼹鼠接待了ta
Mole mole = new Mole();
mole.accept(animalVisitor);
}
}
我来拜访你了,小青蛙 >_< !!!
我来拜访你了,小鸭子 >_< !!!
我来拜访你了,小乌鸦 >_< !!!
我来拜访你了,小鼹鼠 >_< !!!
☘️ 使用场景
◾️如果你需要对一个复杂对象结构(例如对象树)中的所有元素执行某些操作,可使用访问者模式。
访问者模式通过在访问者对象中为多个目标类提供相同操作的变体,让你能在属于不同类的一组对象上执行同一操作。
◾️可使用访问者模式来清理辅助行为的业务逻辑。
该模式会将所有非主要的行为抽取到一组访问者类中,使得程序的主要类能更专注于主要的工作。
◾️当某个行为仅在类层次结构中的一些类中有意义,而在其他类中没有意义时,可使用该模式。
你可将该行为抽取到单独的访问者类中,只需实现接收相关类的对象作为参数的访问者方法并将其他方法留空即可。
🧊 实现方式
(1)在访问者接口中声明一组“访问”方法,分别对应程序中的每个具体元素类。
(2)声明元素接口。如果程序中已有元素类层次接口,可在层次结构基类中添加抽象的“接收”方法。该方法必须接受访问者对象作为参数。
(3)在所有具体元素类中实现接收方法。这些方法必须将调用重定向到当前元素对应的访问者对象中的访问者方法上。
(4)元素类只能通过访问者接口与访问者进行交互。不过访问者必须知晓所有的具体元素类,因为这些类在访问者方法中都被作为参数类型引用。
(5)为每个无法在元素层次结构中实现的行为创建一个具体访问者类并实现所有的访问者方法。
(你可能会遇到访问者需要访问元素类的部分私有成员变量的情况。在这种情况下,你要么将这些变量或方法设为公有,这将破坏元素的封装;要么将访问者类嵌入到元素类中。后一种方式只有在支持嵌套类的编程语言中才可能实现。)
(6)客户端必须创建访问者对象并通过“接收”方法将其传递给元素。
🎲 优缺点
➕ 访问者对象可以在与各种对象交互时收集一些有用的信息。当你想要遍历一些复杂的对象结构(例如对象树),并在结构中的每个对象上应用访问者时,这些信息可能会有所帮助。
➕ 开闭原则。你可以引入在不同类对象上执行的新行为,且无需对这些类做出修改。
➕ 单一职责原则。可将同一行为的不同版本移到同一个类中。
➖ 每次在元素层次结构中添加或移除一个类时,你都要更新所有的访问者。
➖ 在访问者同某个元素进行交互时,它们可能没有访问元素私有成员变量和方法的必要权限。
➖ 具体元素对访问者公布细节,违反了迪米特原则。
➖ 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
🌸 补充
访问者模式被称为行为模式中最难的一个模式,但它就是一个简单的“调用主体反转”,不是吗?