系列文章目录
创建型模式 - 单例模式(一)
创建型模式 - 工厂模式(二)
创建型模式 - 原型模式(三)
创建型模式 - 建造者模式(四)
结构型模式 - 适配器模式(一)
结构型模式 - 桥接模式(二)
结构型模式 - 装饰器模式(三)
结构型模式 - 组合模式(四)
结构型模式 - 外观模式(五)
结构型模式 - 享元模式(六)
结构型模式 - 代理模式(七)
行为型模式 - 模板方法模式(一)
行为型模式 - 命令模式(二)
行为型模式 - 访问者模式(三)
行为型模式 - 迭代器模式(四)
行为型模式 - 观察者模式(五)
行为型模式 - 中介者模式(六)
行为型模式 - 备忘录模式(七)
行为型模式 - 解释器模式(八)
行为型模式 - 状态模式(九)
行为型模式 - 策略模式(十)
行为型模式 - 责任链模式(十一)
前言
一、访问者模式
1.1 访问者模式介绍
- 访问者(Visitor)模式:
- 将作用于
某种数据结构(比如对象)
中的各元素
的操作分离
出来封装成独立的类
,使其在不改变数据结构中的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式;- 访问者模式是对数据的操作与数据结构进行分离,解决
数据结构
和操作耦合性
的问题;- 基本原理:
在被访问的类里面加一个对外提供接待访问者的接口
;
1.2 访问者模式结构
抽象访问者(Visitor)角色:
- 定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作,该操作中的参数类型标识了被访问的具体元素;
具体访问者(Concrete Visitor)角色:
- 实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么;
抽象元素(Element)角色:
- 声明一个包含接收操作的接口,被接收的访问者对象作为方法的参数;
具体元素(Concrete Element)角色:
- 实现抽象元素角色提供的操作,另外具体元素中可能还包含本身业务逻辑的相关操作;
对象结构(Object Structure)角色:
- 是一个包含元素角色的容器,提供访问者对象遍历容器中的所有元素的方法,通常由List、Set、Map等聚合类实现;
二、实现
例子:
- 在中国好声音上面,歌手唱歌,导师(有男有女)评价要不要这个歌手;
- 如果采用继承的方式,判断成功做什么失败做什么操作,在系统小的时候还是可以的,但是考虑系统增加越来越多的功能是,对代码改动较大,违反开闭原则;
- 另外扩展性也不好,比如上面增加个导师或者结果呢?那么有有没有别的办法呢?
2.1 访问者实现
package com.dozezz.designpattern.visitor;
import java.util.ArrayList;
import java.util.List;
/**
* 数据结构
*/
public class ObjectStructure {
private List<Element> elementList = new ArrayList<>();
public void addElement(Element element) {
elementList.add(element);
}
public void removeElement(Element element) {
elementList.remove(element);
}
/**
* 演进①:如果增加一个访问者,也就是对这个歌手评价的人,比如老人、小孩,就要新增if else
* 在比如增加一个评论结果呢? 比如变为成功、失败、待定,那么是不是还要增加一个元素的 instanceof 判断在处理?
*
* 不管是新增评论者,还是新增评论结果,都不符合开闭原则,且判断逻辑复杂混乱;
* @param visitor
*/
public void displayElement0(String visitor) {
if ("张三".equals(visitor)) {
for (Element element : elementList) {
if (element instanceof SuccessElement) {
} else if (element instanceof FailedElement) {
}
}
} else if ("李四".equals(visitor)) {
for (Element element : elementList) {
if (element instanceof SuccessElement) {
} else if (element instanceof FailedElement) {
}
}
}
}
/**
* 演进②:上面的程序的主要问题在于双重判断,一层是访问者是谁,一层是给出的结论类型,所以造成了逻辑复杂,扩展性差;
*
* 所以我们把访问者进行抽象,访问不同的元素
*
* @param visitor
*/
public void displayElement1(Visitor visitor) {
for (Element element : elementList) {
if (element instanceof SuccessElement) {
visitor.obtainSuccess((SuccessElement) element);
}
if (element instanceof FailedElement) {
visitor.obtainFailed((FailedElement) element);
}
}
}
/**
* 演进③:新增访问者对这个对象结构的访问时,客户端可以直接使用,不需要修改对象结构了,但是如果增加评论结果时候,那么又完蛋了,还得改代码 instanceOf
*
* 那么怎么去掉这层判断呢?
*
* 我们分析下原因,为什么会出现这个判断呢?我们只知道抽象元素,无法确定它的具体类型,所以没有办法直接调用具体元素的方法;
*
* 那么我们如何确定其具体元素的类型呢?问题也就是转为 到底怎么判断 Element 元素的具体类型或者到底谁知道Element的具体类型
* 1、instanceOf 能得出(上面就是instanceOf 实现,导致扩展不行) 2、具体元素它自己(定义一个方法获取自己 this,也就是当前对象的真实类型)
*
* 1是不同了,2要怎么操作呢?
*
* 在抽象元素内部定义一个方法获取自己的当前对象的真实类型,吧这个类型给访问者不就行了么
*/
public void displayElement(Visitor visitor) {
for (Element element : elementList) {
element.accept(visitor);
}
}
}
package com.dozezz.designpattern.visitor;
/**
* @Description: 抽象访问者
*/
public abstract class Visitor {
/**
* 给出成功评价
*/
public abstract void obtainSuccess(SuccessElement successElement);
/**
* 给出失败评价
*/
public abstract void obtainFailed(FailedElement failedElement);
}
package com.dozezz.designpattern.visitor;
/**
* 具体访问者
*/
public class ManVisitor extends Visitor{
@Override
public void obtainSuccess(SuccessElement successElement) {
System.out.println(String.format("%s,给出了%s","李四", successElement.operationSuccess()));
}
@Override
public void obtainFailed(FailedElement failedElement) {
System.out.println(String.format("%s,给出了%s","李四", failedElement.operationFailed()));
}
}
package com.dozezz.designpattern.visitor;
/**
* 具体访问者
*/
public class WomanVisitor extends Visitor{
@Override
public void obtainSuccess(SuccessElement successElement) {
System.out.println(String.format("%s,给出了%s","张三", successElement.operationSuccess()));
}
@Override
public void obtainFailed(FailedElement failedElement) {
System.out.println(String.format("%s,给出了%s","张三", failedElement.operationFailed()));
}
}
package com.dozezz.designpattern.visitor;
/**
* 抽象元素
*/
public abstract class Element {
/**
* 获取自己的类型,并传递给访问者
* @param visitor
*/
abstract void accept(Visitor visitor);
}
package com.dozezz.designpattern.visitor;
/**
* 具体元素类
*/
public class FailedElement extends Element{
public String operationFailed(){
return "晋级失败 。。 ";
}
@Override
void accept(Visitor visitor) {
visitor.obtainFailed(this);
}
}
package com.dozezz.designpattern.visitor;
/**
* 具体元素类
*/
public class SuccessElement extends Element{
public String operationSuccess(){
return "晋级成功。。";
}
@Override
void accept(Visitor visitor) {
visitor.obtainSuccess(this);
}
}
package com.dozezz.designpattern.visitor;
/**
* 主測試類
*/
public class ClientTest {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.addElement(new SuccessElement());
objectStructure.addElement(new FailedElement());
Visitor visitor = new ManVisitor();
Visitor womanVisitor = new WomanVisitor();
objectStructure.displayElement(visitor);
objectStructure.displayElement(womanVisitor);
}
}
三、访问者模式总结
3.1 访问者模式应用场景
- 对象结构相对稳定,但其操作算法经常变化的程序;
- 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免这些操作的变化影响对象的结构;
- 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作;
3.2 访问者模式注意细节
- 访问者模式灵活的处理了不同类型的换上,面对不同的访问者,有不同的行为的场景;
- 访问者模式倾向于扩展元素的行为,当扩展元素行为时,满足开闭原则,当扩展新的元素类型时,将产生巨大的改动,每一个访问者都需要变动,所以在使用访问者模式时要考虑清楚元素的变化的可能;
- 访问者模式依赖的是具体元素而不是抽象元素,难以扩展;
四、参考文献
- http://c.biancheng.net/view/1376.html
- https://www.cnblogs.com/noteless/p/10178477.html
- https://www.bilibili.com/video/BV1G4411c7N4?p=54&spm_id_from=pageDriver