有时候不想动脑子,就懒得看源码又不像浪费时间所以会看看书,但是又记不住,所以决定开始写"抄书"系列。本系列大部分内容都是来源于《 图解设计模式》(【日】结城浩 著)。该系列文章可随意转载。
Visitor 模式:访问数据结构并处理数据
在 Visitor 模式中,数据结构和数据被分离开来。我们需要编写一个“访问者”的类来访问数据结构中的元素,并把对各元素的处理交给访问者类。这样当需要增加新的处理时,我们只需要编写新的访问者,然后让数据结构可以接受访问者的访问即可。
Visitor 模式 中出场的角色:
类图如下:
Demo如下:使用 ListVisitor 来访问 rootDir 目录下的资源
public interface Element {
void accept(Visitor visitor);
}
public interface Entry extends Element {
/**
* 获取文件名
*
* @return
*/
String getName();
/**
* 获取文件大小
*
* @return
*/
int getSize();
/**
* 添加目录
*
* @param entry
* @return
*/
default Entry addEntry(Entry entry) {
throw new RuntimeException();
}
/**
* 生成迭代器
* @return
*/
default Iterator<Entry> iterator() {
throw new RuntimeException();
}
/**
* 输出路径
* @return
*/
default String thisPath() {
return getName() + "(" + getSize() + ")";
}
/**
* 暴露出方法供访问者访问
* @param visitor
*/
default void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 访问者接口
public interface Visitor {
/**
* 访问
* @param entry
*/
void visit(Entry entry);
}
// 文件
public class File implements Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return size;
}
}
// 文件夹
public class Directory implements Entry {
private String name;
private List<Entry> entries = Lists.newArrayList();
public Directory(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return entries.stream()
.mapToInt(Entry::getSize)
.sum();
}
@Override
public Entry addEntry(Entry entry) {
entries.add(entry);
return this;
}
@Override
public Iterator<Entry> iterator() {
return entries.iterator();
}
}
public class ListVisitor implements Visitor {
/**
* 当前目录
*/
private String currentDir = "";
@Override
public void visit(Entry entry) {
System.out.println(currentDir + "/" + entry.thisPath());
if (entry instanceof Directory) {
String saveDir = currentDir;
currentDir = currentDir + "/" + entry.getName();
entry.iterator()
.forEachRemaining(sonEntry ->
sonEntry.accept(this));
currentDir = saveDir;
}
}
}
public class VisitorDemoMain {
public static void main(String[] args) {
Entry rootDir = new Directory("root");
Entry binDir = new Directory("bin");
Entry tmpDir = new Directory("tmp");
Entry usrDir = new Directory("usr");
Entry hanakoDir = new Directory("hanako");
usrDir.addEntry(hanakoDir);
rootDir.addEntry(binDir);
rootDir.addEntry(tmpDir);
rootDir.addEntry(usrDir);
hanakoDir.addEntry(new File("memo.tex", 10));
binDir.addEntry(new File("vi", 1000));
binDir.addEntry(new File("latex", 2000));
// ListVisitor 访问者访问rootDir 目录下的文件和文件夹
rootDir.accept(new ListVisitor());
}
}
个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑):
项目 A 中,某一数据内容固定,但是存在多处地方会获取该部分数据,可以通过访问者模式来控制不同的访问者来获取不同部分的数据。
// 书的数据
@Data
public class BookData {
/**
* 作者
*/
private String author;
/**
* 表体
*/
private String title;
/**
* 内容
*/
private String content;
/**
* 接受访问者访问
*
* @param bookVisitor
*/
public void accept(BookVisitor bookVisitor) {
if (bookVisitor instanceof TitleBookVisitor) {
bookVisitor.visit(title);
} else if (bookVisitor instanceof AuthorBookVisitor) {
bookVisitor.visit(author);
} else if (bookVisitor instanceof ContentBookVisitor) {
bookVisitor.visit(content);
} else {
throw new RuntimeException("未知的访问者");
}
}
}
// 书籍访问者
public interface BookVisitor {
/**
* 访问数据
* @param data
*/
void visit(String data);
}
// 作者访问
public class AuthorBookVisitor implements BookVisitor {
@Override
public void visit(String data) {
System.out.println("author = " + data);
}
}
// 标题访问
public class TitleBookVisitor implements BookVisitor {
@Override
public void visit(String data) {
System.out.println("title = " + data);
}
}
// 内容访问
public class ContentBookVisitor implements BookVisitor {
@Override
public void visit(String data) {
System.out.println("content = " + data);
}
}
public class DemoMain {
public static void main(String[] args) {
final BookData bookData = new BookData();
bookData.setAuthor("夏义春");
bookData.setTitle("夏义春的一生");
bookData.setContent("夏义春一生天下第一");
bookData.accept(new AuthorBookVisitor());
bookData.accept(new TitleBookVisitor());
bookData.accept(new ContentBookVisitor());
}
}
输入内容
数据结构是单一一个接口或实例,而数据是一个实例,当需要访问数据时,将 数据结构 API 传给数据就可以,这样的话对后面切换数据结构访问非常容易。当需要增加新的处理时,只需要编写新的访问者,然后让数据结构可以接受访问者访问即可。但是相对的,对于数据结构的设计需要具有一定前瞻性。
通常在以下情况可以考虑使用访问者(Visitor)模式:
扩展思路:
双重分发:
我们来整理下 Visitor 模式中方法的调用关系
accept(接受)方法的调用方式如下:
element.accept(visitor);
visit(访问)方法的调用方式如下:
visitor.visit(elemnt)
对比一下就会发现,上面两个方法是相反的关系。element 接受 visitor, 而 visitor 又访问 element。在Visitor 模式中 ConcreteElement 和 ConcreteVisitor 这两个角色共同决定了实际进行的处理。这种消息分发的方式一般被称为双重分发。
为什么如此复杂:
Visitor 把简单的问题复杂化了吗?如果需要循环处理,在数据结构的类中直接编写循环语句不就解决了吗?为什么要搞出 accept 方法 和 visit 方法之间那样复杂的调用关系呢?
Visitor 模式的目的是将处理从数据结果中分离出来。数据结构很重要,它能将元素集合和关联在一起,但是需要注意的是,保存数据结构与以数据结构为基础进行处理是两个不同的东西。
在示例程序中我们创建了 ListVisitor 类作为显示文件夹内容的 ConcreteVisitor 角色。通常,ConcreteVisitor 角色的开发可以独立于 File 类 和 Directory 类,也就是说 Visitor 模式提高了File类和 Directory 类作为组件的独立性。如果将进行处理的方法定义在 File 类和 Directory 类作为组件的独立性。如果将进行处理的方法定义在 File类和 Directory 类中,当每次要扩展功能,增加新的处理时就不得不去修改 File 类和 Directory类。
易于增加 ConcreteVisitor 角色
使用 Visitor 模式可以很容易地修改 ConcreteVisitor 角色,因为具体的处理被交给 ConcreteVisitor角色负责,因此完全不用修改 ConcreteElement 角色。
难以增加 ConcreteElement 角色
虽然使用 Visitor 模式可以很容易地增加 ConcreteVisitor 角色,不过他却很难应对 ConcreteElement 角色的增加。
Visitor 工作所需的条件 : Element 角色必须向 Visitor 角色公开足够多的信息。不过也可以基于此限制访问者能获取到的数据
相关设计模式:
一时的小想法,仅仅个人理解,无需在意 :
Chain of Responsibility 模式 : 推卸责任
当外部请求程序进行某个处理,但程序暂时无法直接决定由哪个对象负责处理时,就需要推卸责任,在这种情况下,我们可以考虑将多个对象组成一条职责链。然后按照他们在职责链上的顺序一个一个地找出到底应该谁负来负责处理。这种模式称之为 Chain of Responsibility 模式。
Chain of Responsibility模式 中出场的角色:
类图如下:
Demo 如下:可以看到不同的问题会交由不同的 Support 来解决,当一个 Support 不能解决时会交由 next 来处理
public abstract class Support {
/**
* 下一个解决器
*/
private Support next;
/**
* 设置下一个解决器
* @param next
* @return
*/
public Support setNext(Support next) {
this.next = next;
return this;
}
/**
* 直接解决问题
* @param trouble
*/
public final void support(String trouble) {
if (resolve(trouble)) {
done(trouble);
} else if (next != null) {
next.support(trouble);
} else {
fail(trouble);
}
}
/**
* 解决问题
*
* @param trouble
* @return
*/
protected abstract boolean resolve(String trouble);
/**
* 问题已解决
*
* @param trouble
*/
protected void done(String trouble) {
System.out.println(trouble + " 问题已被 " + this.getClass().getSimpleName() + " 解决");
}
/**
* 问题未解决
*
* @param trouble
*/
protected void fail(String trouble) {
System.out.println(trouble + " 问题未解决");
}
}
// 解决特定编号的问题
public class SpecialSupport extends Support {
@Override
protected boolean resolve(String trouble) {
return "夏义春".equals(trouble);
}
}
// 解决小于指定长度的问题
public class LimitSupport extends Support {
@Override
protected boolean resolve(String trouble) {
return StringUtils.length(trouble) < 10;
}
}
// 不解决任何问题
public class NoSupport extends Support {
@Override
protected boolean resolve(String trouble) {
return false;
}
}
public class ChainDemoMain {
public static void main(String[] args) {
Support noSupport = new NoSupport();
Support limitSupport = new LimitSupport();
Support specialSupport = new SpecialSupport();
// 构建责任链
specialSupport.setNext(limitSupport.setNext(noSupport));
specialSupport.support("你好");
specialSupport.support("夏义春");
specialSupport.support("1234567890");
}
}
输出如下:
责任链模式在很多地方都有所使用,如 Spring中的过滤器、拦截器、Dubbo中的 Filter, Spring Cloud Gateway 中的 GlobalFilter 等等。这里以Spring的 拦截器为例。HandlerInterceptorAdapter 定义如下,
public abstract class HandlerInterceptorAdapter implements HandlerInterceptor {
/**
* This implementation always returns true
.
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
/**
* This implementation is empty.
*/
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}
/**
* This implementation is empty.
*/
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
在 DispatcherServlet#doDispatch 中,Spring 在分发请求的前后都会将请求交由 HandlerInterceptor 的责任链来执行一遍,具体的代码在 HandlerExecutionChain 中,如下是其中一小部分:
...
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 遍历所有注册的拦截器并依次触发
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}
...
个人使用:该部分内容是写给自己看的,帮助自身理解,因此就不交代项目背景了,读者请自行忽略(◐ˍ◑):
在项目A 中,需要对客户进行计费,客户可以自定义计费规则组合,因此对于每种计费规则都是一个实现类,这里实现则是将每个多个客户的计费规则组成成一个链路,即责任链。和上面的区别在于,上面的场景当某一个 Support 处理完成后就不再执行下面的Support,而这里的情况则是需要将链路中所有可以处理当前情况的Support 全部执行,得到最终结果。Demo如下:
public abstract class Rule {
/**
* 下一个规则
*/
@Setter
private Rule next;
/**
* 处理费用
*
* @param fee
*/
public final String handle(String fee) {
fee = doHandle(fee);
if (next != null) {
return next.handle(fee);
}
return fee;
}
/**
* 处理
*
* @param fee
*/
protected abstract String doHandle(String fee);
}
public class VipRule extends Rule {
// 处理 VIP 逻辑
@Override
protected String doHandle(String fee) {
return fee.contains("vip") ? fee + "vip nb" : fee;
}
}
public class SpecialRule extends Rule {
// 客户定制化逻辑
@Override
protected String doHandle(String fee) {
return fee.length() > 5 ? fee + " 长度大于5" : fee + " 长度小于5";
}
}
public class DefaultRule extends Rule {
@Override
protected String doHandle(String fee) {
return fee;
}
}
public class DemoMain {
public static void main(String[] args) {
final Rule vipRule = new VipRule();
final Rule specialRule = new SpecialRule();
final Rule defaultRule = new DefaultRule();
vipRule.setNext(specialRule);
specialRule.setNext(defaultRule);
System.out.println(vipRule.handle("vip 001"));
System.out.println("----------------");
System.out.println(vipRule.handle("夏义春 001"));
}
}
输出如下:
在项目B中,每个客户可以定制各自的单证处理逻辑,多个逻辑可以组成,形成链路后,依次执行逻辑,基本同项目 A 的情况,这里不再赘述。
扩展思路
相关设计模式
一时的小想法,仅仅个人理解,无需在意 :
https://www.cnblogs.com/yb-ken/p/15084837.html