设计模式实战-访问者模式,减少代码体积
1、定义
访问者模式(Visitor Pattern)的英文定义如下:
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
意思是:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。简单地来说,就是将数据结构和数据操作相分离。
比如说,做过开发的都知道,普通用户和 Root 用户在 Linux 机器上对文件或者文件夹进行操作时,Root 用户往往不受限制,而普通用户却需要各种访问权限才能正常进行,这种同样是访问文件或文件夹,不同的访问者表现出的行为不同的现象就是我们的访问者模式,文件或文件夹就是被访问的元素,Root 用户或者普通用户就是我们的访问者,而文件或文件夹往往是位于计算机或者其它存储设备上的,这里的存储设备就是访问者模式中的 ObjectStructure,可以类比为元素的容器对象。
2、组成角色
访问者模式包含角色如下:
- 访问者(Visitor):定义对不同的元素进行访问时的抽象行为,一般来说,有多少个具体元素,就有多少个抽象接口;
- 具体访问者(ConcreteVisitor):实现上面 Visitor 定义的所有接口,用来指定该访问者对各个元素进行访问时的具体行为,在本文中由 Root 用户和普通用户扮演该角色;
- 元素(Element):抽象的被访问的元素,一般会定义一个 accept 方法,指定其被访问时的抽象行为;
- 具体元素(ConcreteElement):具体的被访问的元素,实现上面 Element 的 accept 方法,各个元素负责定义自己的 accept 行为,来表示其被访问时的行为,本文中由 FileElement 和 DictionaryElement 类扮演;
- 对象结构(ObjectStructure):对象结构实际上是一个被访问元素的集合,好比一个元素容器,对容器的具体元素的访问表现出的行为如何,这是由访问者模式决定的。
角色之间的 UML 关系图如下:
3、访问者模式代码实现
3.1 抽象访问者
// 抽象的访问者角色,需要针对每个被访问元素都定义一个接口
public interface Visitor {
void visit(ConcreteElementA element);
}
3.2 具体访问者
// 具体的访问者
public class ConcreteVisitorA implements Visitor {
@Override
public void visit(ConcreteElementA element){
// 实现自己的对元素ConcreteElementA的访问行为
}
}
3.3 抽象元素
// 被访问元素的抽象
public interface Element {
// 声明 accept 方法,代表元素可以被访问
void accept(Visitor visitor);
}
3.4 具体元素
// 实现抽象元素接口
public class ConcreteElementA implements Element {
@Override
public void accept(Visitor visitor) {
// 传入对象自身
visitor.visit(this);
}
}
3.5 对象结构
public class ObjectStructure {
private List<Element> elements;
public ObjectStructure(List<Element> elements) {
this.elements = elements;
}
// 自定义方法,该方法往往要对 elements 进行遍历
public void show(Visitor visitor) {
for(Element element: elements) {
element.accept(visitor);
}
}
}
4、优缺点
访问者模式的优点:
- 数据结构和数据操作相分离;
- 对访问者拓展性良好,只需要增加新的访问者类即可;
- 各个角色职责明确,符合单一职责原则。
访问者模式的缺点:
- 元素变更时会导致整个代码都要调整。
5、应用场景
访问者模式的典型应用场景如下:
- 对象的结构(元素)比较稳定,而访问者频繁变动的场景;
- 数据操作和数据结构分离的场景。
6、使用实例
还是以文章开始讲的 Root 用户和普通用户访问文件或文件夹为例,下面我们以访问者模式来实现一下:
6.1 抽象的访问元素
// 被访问的元素
interface Element {
void accept(Visitor visitor);
}
6.2 具体访问元素 —— 文件元素
// 文件元素
class FileElement implements Element {
// 当前元素的访问权限
private String lookPerms;
// 文件名
private String name;
FileElement(String name, String lookPerms) {
this.name = name;
this.lookPerms = lookPerms;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
String getName() {
return name;
}
String getLookPerms() {
return lookPerms;
}
}
6.3 具体访问元素 —— 文件夹元素
// 文件夹
class DictionaryElement implements Element {
// 当前元素的访问权限
private String lookPerms;
// 文件夹名
private String name;
DictionaryElement(String name, String lookPerms) {
this.name = name;
this.lookPerms = lookPerms;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
String getName() {
return name;
}
String getLookPerms() {
return lookPerms;
}
}
6.4 抽象的访问者
// 访问者
interface Visitor {
// 定义对不同的元素(文件)进行访问时的具体行为
void visit(FileElement fileElement);
// 定义对不同的元素(文件夹)进行访问时的具体行为
void visit(DictionaryElement dictionaryElement);
}
6.5 具体访问者 ——Root 用户
// root用户
class RootVisitor implements Visitor {
@Override
public void visit(FileElement fileElement) {
System.out.println("当前:Root " + "要访问的文件名:" + fileElement.getName() + " 允许访问!");
}
@Override
public void visit(DictionaryElement dictionaryElement) {
System.out.println("当前:Root " + "要访问的文件夹名:" + dictionaryElement.getName() + " 允许访问!");
}
}
6.6 具体访问者 —— 普通用户
// 普通用户
class NormalVisitor implements Visitor {
// 定义该用户具备的权限集合
private List<String> lookPerms;
NormalVisitor(List<String> lookPerms) {
this.lookPerms = lookPerms;
}
@Override
public void visit(FileElement fileElement) {
if (this.lookPerms.indexOf(fileElement.getLookPerms()) >= 0) {
System.out.println("当前:普通用户" + " 具备权限:" + this.lookPerms.toString() + " 要访问的文件名:" + fileElement.getName() + " 允许访问!");
} else {
System.out.println("当前:普通用户" + " 具备权限:" + this.lookPerms.toString() +" 要访问的文件名:" + fileElement.getName() + " 权限不足,禁止访问!");
}
}
@Override
public void visit(DictionaryElement dictionaryElement) {
if (this.lookPerms.indexOf(dictionaryElement.getLookPerms()) >= 0) {
System.out.println("当前:普通用户" + " 具备权限:" + this.lookPerms.toString() + " 要访问的文件夹名:" + dictionaryElement.getName() + " 允许访问!");
} else {
System.out.println("当前:普通用户" + " 具备权限:" + this.lookPerms.toString() +" 要访问的文件夹名:" + dictionaryElement.getName() + " 权限不足,禁止访问!");
}
}
}
6.7 存储设备
// ObjectStructure角色
class Computor {
// 计算机中的文件和文件夹List
private List<Element> elementList = new LinkedList<>();
{
elementList.add(new FileElement("Java讲义.pdf", "look-file"));
elementList.add(new DictionaryElement("program", "look-dictionary"));
}
// 展示该电脑中的文件和文件夹
void showFileAndDict(Visitor visitor) {
for (Element element: elementList) {
element.accept(visitor);
}
}
}
6.8 客户端
public static void main(String[] args) {
Computor computor = new Computor();
computor.showFileAndDict(new NormalVisitor(Collections.singletonList("look-dictionary")));
computor.showFileAndDict(new RootVisitor());
computor.showFileAndDict(new NormalVisitor(Arrays.asList("look-file", "look-dictionary")));
}
输出结果如下:
当前:普通用户 具备权限:[look-dictionary] 要访问的文件名:Java 讲义.pdf 权限不足,禁止访问! 当前:普通用户 具备权限:[look-dictionary] 要访问的文件夹名:program 允许访问! 当前:Root 要访问的文件名:Java 讲义.pdf 允许访问! 当前:Root 要访问的文件夹名:program 允许访问! 当前:普通用户 具备权限:[look-file, look-dictionary] 要访问的文件名:Java 讲义.pdf 允许访问! 当前:普通用户 具备权限:[look-file, look-dictionary] 要访问的文件夹名:program 允许访问!
7、总结
访问者模式适应于元素种类基本不变但是 visit 行为变化的场景,或者说访问者不断增加的场景,访问者增加时我们只需要增加新的访问者类即可,一定程度上避免了在 visit 上进行 if…else 的繁杂逻辑判断,减少了代码体积
- 1分钟懂awk-技不在深,够用就行
- RChain节点通信机制
- Spark源码系列(一)spark-submit提交作业过程
- Android安全几道入门题目
- 挖洞经验 | 看我如何通过子域名接管绕过Uber单点登录认证机制
- Spark源码系列(二)RDD详解
- Spark源码系列(三)作业运行过程
- Spark源码系列(四)图解作业生命周期
- Spark源码系列(五)分布式缓存
- 看我如何基于Python;Facepp打造智能监控系统
- Spark源码系列(六)Shuffle的过程解析
- Spark源码系列(九)Spark SQL初体验之解析过程详解
- Spark源码系列(七)Spark on yarn具体实现
- 我们要在任何可能的地方测试XSS漏洞
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- PHP开发的文字水印,缩略图,图片水印实现类与用法示例
- spring-boot-route(二十)Spring Task实现简单定时任务
- php+ajax实现商品对比功能示例
- spring-boot-route(二十一)Quartz实现动态定时任务
- Helm仓库的重要变更和v2支持将在11月结束
- ThinkPHP框架整合微信支付之刷卡模式图文详解
- 详解Python直接赋值,深拷贝和浅拷贝
- 利用python下载scihub成文献为PDF操作
- PHP中的Iterator迭代对象属性详解
- 使用Python实现微信拍一拍功能的思路代码
- Laravel5.7 Eloquent ORM快速入门详解
- 微信公众号用户与网站用户的绑定解决方案分析
- laravel使用Faker数据填充的实现方法
- PHP开发实现快递查询功能详解
- Laravel5.7 数据库操作迁移的实现方法