一、访问者模式介绍
在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。
主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题。
访问者模式角色:
- 抽象访问者角色(
Visitor
):为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色,这样访问者就可以通过该元素角色的特定接口直接访问它。- 具体访问者角色(
ConcreteVisitor
):实现Visitor声明的接口。- 抽象元素角色(
Element
):定义一个接受访问操作(accept()
),它以一个访问者(Visitor)作为参数。- 具体元素(
ConcreteElement
):实现了抽象元素(Element)所定义的接受操作接口。此处我们有一个需求:
- 评委老师通过给予选手通过或者不通过来进行打分。
- 传统方式实现:将评委作为对象,打分通过条件去进行判断。
- 传统方式问题:当评委名单变更,或者打分新增“待定”时,需要直接直接在代码上进行改动。违反了
OCP
原则,不利于维护。- 解决方式:通过访问者模式进行实现。
二、访问者模式使用
2.1 示例关系:
2.2 代码实现:
- 抽象元素及具体元素
/* *
* 1. 评委接口(Element)。
*/
interface Judge {
/* *
* 定义一个接受访问操作(accept()),它以一个访问者(Visitor)作为参数。
*/
void accept(Evaluation evaluation);
}
/* *
* 2. JayChou类实现评委接口(ConcreteElement)。
*/
class JayChou implements Judge {
public String getName() {
return "周杰伦评委";
}
@Override
public void accept(Evaluation evaluation) {
evaluation.getJayChouEval(this);
}
}
/* *
* 3. FayeWong类实现评委接口(ConcreteElement)。
* ConcreteElement角色使用到了“双分派”。
* 第一次分派:客户端调用,将具体状态作为参数传入FayeWong/JayChou中。
* 第二次分派:getFayeWong/JayChouEval(),将自己(this)作为参数。
* 双分派:执行操作取决于请求的种类和接收者类型(即不管类怎么变化,总能以期望的方法运行)。
*/
class FayeWong implements Judge {
public String getName() {
return "王菲评委";
}
@Override
public void accept(Evaluation evaluation) {
evaluation.getFayeWongEval(this);
}
}
- 抽象访问者及具体访问者
/* *
* 4. 评价抽象类(Visitor)。
*/
abstract class Evaluation {
/* *
* 得到具体评委的评价。
*/
public abstract void getJayChouEval(JayChou jayChou);
public abstract void getFayeWongEval(FayeWong fayeWong);
}
/* *
* 5. “通过评价类”继承评价抽象类(ConcreteVisitor)。
*/
class Pass extends Evaluation {
@Override
public void getJayChouEval(JayChou jayChou) {
System.out.println(jayChou.getName() + "打分通过。");
}
@Override
public void getFayeWongEval(FayeWong fayeWong) {
System.out.println(fayeWong.getName() + "打分通过。");
}
}
/* *
* 6. “不通过评价类”继承评价抽象类(ConcreteVisitor)。
*/
class Fail extends Evaluation {
@Override
public void getJayChouEval(JayChou jayChou) {
System.out.println(jayChou.getName() + "打分不通过。");
}
@Override
public void getFayeWongEval(FayeWong fayeWong) {
System.out.println(fayeWong.getName() + "打分不通过。");
}
}
- 结构对象角色
/* *
* 7. 结构对象角色。
* 它是使用访问者模式必备的角色。
* 作用:能枚举它的元素;可以提供一个高层接口以允许访问者访问它的元素。
*/
class ObjectStructure {
/* *
* 维护和管理评委的集合。
*/
List<Judge> judges = new LinkedList<>();
public void addJudge(Judge judge) {
judges.add(judge);
}
public void removeJudge(Judge judge) {
judges.remove(judge);
}
/* *
* 展示评价信息。
*/
public void showEval(Evaluation eval) {
for (Judge judge : judges) {
judge.accept(eval);
}
}
}
- 客户端调用
/* *
* 8. 客户端调用。
*/
public class Client {
public static void main(String[] args) {
// 创建结构对象角色。
ObjectStructure objectStructure = new ObjectStructure();
// 添加元素(评委)给结构角色。
objectStructure.addJudge(new FayeWong());
objectStructure.addJudge(new JayChou());
// 创建具体访问者(评价)。
Pass pass = new Pass();
Fail fail = new Fail();
// 通过结构角色进行元素枚举。
objectStructure.showEval(pass);
// 王菲评委打分通过。
// 周杰伦评委打分通过。
System.out.println();
objectStructure.showEval(fail);
// 王菲评委打分不通过。
// 周杰伦评委打分不通过。
}
}
三、访问者模式总结
优点:
- 符合单一职责原则,让程序具有优秀的拓展性、灵活性。
- 可以对功能进行统一,可以做报表、UI、拦截器与过滤器。适用于数据结构相对稳定的系统。
缺点:
- 具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,一定程度上违背了迪米特法则,这会造成具体元素变更比较困难。
- 违背了依赖倒转原则,访问依赖的是具体的元素,而不是抽象的元素。
应用场景:
- 需要面对一个对象结构中的对象进行很多不同的操作(这些操作彼此没有关联),同时需要避免让这些操作“污染”这些对象的类,可以选用访问者模式解决。
四、结束语
“-------怕什么真理无穷,进一寸有一寸的欢喜。”
微信公众号搜索:饺子泡牛奶。