在诸多设计模式中,个人一直很难理解解释器模式和访问者模式【可能用相对少吧】。这两天决定去再次认真看看访问者模式,在网上搜了搜,发现给出的答案往往太过于“为了演示模式而举例“,看了之后还是一头雾水【知道了如何用,却很难理解为什么要用- -】,最终在网上找到一篇个人觉得举例还算贴近生活【具体作者忘了,在此说声谢谢】,看完后,加上自己的一番思考,方有了本篇博文。
还是以网上那篇博文进行举例【但是又做了些许修改和个人思考过程,觉得这样方便理解】
场景: 交通工具在不同时期价格的浮动。
首先看看没有使用访问者模式的情况:
有火车(Train)和汽车(Bus)两种交通工具(Vehicle)【你可以想象有很多交通工具,飞机,动车。。。】,现在要求在过年期间价格上调。代码如下:
//Vehicle.java
package com.xiaolu.normal;
public interface Vehicle {
public void showPrice();
public void priceChange();
}
//Bus.java
package com.xiaolu.normal;
public class Bus implements Vehicle {
@Override
public void showPrice() {
System.out.println("汽车当前票价为80元");
}
@Override
public void priceChange() {
System.out.println("过年了,价格上涨5%");
}
}
//Train.java
package com.xiaolu.normal;
public class Train implements Vehicle {
@Override
public void showPrice() {
System.out.println("火车当前票价为 50元");
}
@Override
public void priceChange() {
System.out.println("过年高峰期,在原有基础上价格上调10%");
}
}
//Client.java --客户端
package com.xiaolu.normal;
public class Client{
public static void main(String[] args) {
Vehicle v = new Bus();
v.showPrice();
v.priceChange();
System.out.println("--------------------------------------------------");
v = new Train();
v.showPrice();
v.priceChange();
}
}
输出:
汽车当前票价为80元
过年了,价格上涨5%
--------------------------------------------------
火车当前票价为 50元
过年高峰期,在原有基础上价格上调10%
现在需求有变化,要求在国庆期间价格在原有基础上【平时的价格】上调 一些,怎么办呢?我们如何应对这个需求?
修改Vehicle接口?那么势必它的所有实现者都必须跟着变【实现者可能非常多额】,好,你忍受了这次痛苦,那么下次需求说中秋节价格也要变化呢?是否又得修改接口呢? 这还只是一个维度的变化,如果需求要求车辆可以显示出自己的最高运行速度呢???接口又要变化吗?
我记得某本软件工程类书中说过”不要被同一颗子弹打中两次“【或者说”戏弄我一次,是你蠢;戏弄我两次,是我蠢 - - 】,那么如果你一直改接口,随着需求的变化你不是被“打中两次”,而是直接“被打死”啊- -
那么接下来,本着访问者模式的实现思想【这里我是选择了逆推,因为没有看过本模式的人,很难自己去思考出来这种方式,我们逆着推,一步步进行,只要最终理解即可】
在需求第一次改变的时候【此时要求国庆也有价格调整】,我们就做出对应的对策:
//Vehicle.java
package com.xiaolu.visitpattern;
public interface Vehicle {
public void showPrice();
/*这里为什么叫做accept,而不是priceChange等等这样更容易表达语意的函数名呢?
* 1 accept对于熟悉设计模式的人,看到这个名称很容易构思到本模式。所以容易理解
*
* 2 我很难想出其他名称,因为接下来的举例你会发现有可能我要实现需求的另一个维度变化
* 比方说显示车辆的最高时速。那么此时priceChange方法名称反而容易让使用者误导。【有时候模糊
* 也是一种“美“ - - ,无奈之下,我选择尊重第一条原因吧】
*/
public void accept(Vistor v);
}
//Vistor.java
package com.xiaolu.visitpattern;
public interface Vistor {
/* 这里有人疑惑 为什么不适用”面向接口编程“,写作
* public void visit(Vehicle v); 呢? 嗯,你是一个爱思考的人,我也是- -)
* 因为有时候 具体的子类可能有有些自己特殊的方法【比如飞机会飞...当然这样做会牺牲掉使用
* Vehicle多态--不是很推荐,但现实中经常需要这样,如果你能确保不必如此,那也可以吧】
*
* 为什么叫做visit而不是其他名称?原因基本和上面【对于为什么叫accept】一样
*/
public void visit(Bus b);
public void visit(Train t);
}
//GQVisitor.java --- 一个具体的访问者,代表国庆价格上调这种行为需求【原谅我可怜的英语水平】
package com.xiaolu.visitpattern;
public class GQVisitor implements Vistor {
@Override
public void visit(Bus b) {
b.showPrice();
System.out.println("国庆期间汽车价格上涨5%");
}
@Override
public void visit(Train t) {
t.showPrice();
System.out.println("国庆期间价格上涨3%");
}
}
//Bus.java
package com.xiaolu.visitpattern;
public class Bus implements Vehicle {
@Override
public void showPrice() {
System.out.println("汽车当前票价为80元");
}
@Override
public void accept(Vistor v) {
v.visit(this); //对于此处,我不想做太多解释,我更希望理解语意【为什么要用访问者模式?】
//如果你不了解这句话,可以搜搜“双分派机制”或者算了,直接放弃这块吧。
//你OO功底不过关- - 。
}
}
// Train.java
package com.xiaolu.visitpattern;
public class Train implements Vehicle {
@Override
public void showPrice() {
System.out.println("火车当前票价为 50元");
}
@Override
public void accept(Vistor v) {
v.visit(this);
}
}
//VisitPatternMain.java ---客户端
package com.xiaolu.visitpattern;
public class VisitPatternMain {
public static void main(String[] args) {
Bus b = new Bus();
Train t = new Train();
Vistor v = new GQVisitor();
v.visit(b);
System.out.println("-------------------------------------");
v.visit(t);
}
}
输出:
汽车当前票价为80元
国庆期间汽车价格上涨5%
-------------------------------------
火车当前票价为 50元
国庆期间价格上涨3%
这个达到了我们的要求,那么此时,需要要求车辆可以显示自己的时速呢?
//RateVistor.java
package com.xiaolu.visitpattern;
public class RateVistor implements Vistor{
@Override
public void visit(Bus b) {
System.out.println(b.getClass().getName() + "速度为80km/h");
}
@Override
public void visit(Train t) {
System.out.println(t.getClass().getName() + "速度为200km/h");
}
}
//VisitPatternMain.java ----客户端
package com.xiaolu.visitpattern;
public class VisitPatternMain {
public static void main(String[] args) {
Bus b = new Bus();
Train t = new Train();
//Vistor v = new GQVisitor(); //我只是修改了一句话,其实吧,我更想使用一个简单工厂直接
//把此处封装起来。不管为了避免过多引入复杂性。忍忍咯- -
Vistor v = new RateVistor();
v.visit(b);
System.out.println("-------------------------------------");
v.visit(t);
}
}
输出:
com.xiaolu.visitpattern.Bus速度为80km/h
-------------------------------------
com.xiaolu.visitpattern.Train速度为200km/h
到这里你疑惑了,为什么没有出现”对象结构“?!就是那个可以遍历啊,枚举元素的东西【类似于java中的Collection的玩意?嗯,好吧,那就加上吧—-
“`
//VehicleCollection.java 充当”对象结构”
package com.xiaolu.visitpattern;
import java.util.LinkedList;
import java.util.List;
public class VehicleCollection {
private List vs = new LinkedList();
public void attach(Vehicle v) {
if (v != null && !vs.contains(v)) {
vs.add(v);
}
}
public void detach(Vehicle v) {
if (v != null && vs.contains(v)) {
vs.remove(v);
}
}
public void accept(Vistor v) {
if (v == null)
return;
for (Vehicle vehicle : vs) {
vehicle.accept(v);
}
}
public void clear() {
if (vs != null && !vs.isEmpty()) {
vs.clear();
}
}
}
//VisitPatternMain2.java —-客户端
package com.xiaolu.visitpattern;
public class VisitPatternMain2 {
public static void main(String[] args) {
VehicleCollection vc = new VehicleCollection();
vc.attach(new Bus());
vc.attach(new Train());
vc.accept(new RateVistor());
}
}
输出:
com.xiaolu.visitpattern.Bus速度为80km/h
com.xiaolu.visitpattern.Train速度为200km/h
如你所感觉,在最后这里,我有一种“为了模式而模式”的无奈感【为了“完整性”还是实现了它】,我觉得最重要的是体会访问者模式这种思路,而不是它规定的必须“有什么“。当然也有可能是我个人
理解的问题,至少目前这个问题背景下,我觉得没有必要,反而”有害“,想想如果Bus或者Train类中恰好有一些自己的”特殊“行为,那么attch进入VehicleCollection后,全部丢失了- -,这有时候不是你想要的吧- -。
说了这么多,优点和缺点依照个人理解总结下:
优点:
有很大的灵活性,想通了原理后,实现起来也不算太麻烦。
缺点:
也可以算是它的不适用场景吧。就是如果”元素“经常有变动,那么就会导致Vistor的接口不断变动。然而无绝对,如果不考虑具体“元素”的特殊性行为,而是在Vistor中直接以 ”元素接口“实现,可能也不会带来太大冲击。【这个没有实践过,是个人推断】
以上是个人理解,如果有错误或者理解不到位的地方,非常欢迎讨论指出,交流使得我们更好的前进。