模式背景: 今天在看<<java 并发编程 设计原则与模式>>中, 涉及到了Visitor模式.
在并发过程中, 当一个类中持有一个集合的时候, 如果需要遍历这个集合,
就不能单纯的考虑把整个集合返回给调用方.因为这样就给调用方提供了改变集合的操作. 试想,当你花费了大量的synchronized方法或者使用了多
么高明的lock-free算法进行性能上的优化,一旦你向外界返回了你要保护的对象,一切都将是一场"灾难".
矛盾的是, 你无法预测客户会使用什么样的方式来进行对你集合中的元素进行操作. 困难如下:
private List<T> yourList;
.....
public synchronized void doIterator(){
for(T t: yourList){
<how to do with element e ...??>
}
}
你如何确认对方需要做的操作时什么呢? 答案是没有. 基于面对对象的操作. 遍历的方式不变, 如果是串行的数据结构,无非就是循环.
如果是树状结构那么可能会不一样的遍历方式. 就如同蛋糕店中一样,他们给你返回了蛋糕,可是要怎么去吃,那是你自己的事情.
想到这点, 就应该清楚一件事情, 调用方的操作理应"自己知道".那么就变成了如下的形式:
private List<T> yourList;
.....
public void doIterator(Client : client){
for(T t: yourList){
client.doWithElemnt(t);
}
}
看到这里, 也许还有问题: 我怎么知道你是使用"doWithElemnt(..)"这个方式去消费呢?
换句话说,这是一种约定,java中,标准时用Interface来表示. 这样就形成了这样的一个"调用方"的结构:
当有client来遍历的时候,就调用它的visit方法,并把蛋糕给他. 事情就是这么简单!
客户的问题解决了,但是客户真正想要的是蛋糕,并不是整个"蛋糕池". 理解这一点很重要,
因为它进一步确定了我们不应该在doIterator方法中调用客户端.
而是应该在List<T>的每一个t上调用,代码进一步更改:
private List<T> yourList; .....
public void doIterator(){
for(T t: yourList){
t.accept(visitor);
}
}
...
T 中的accept方法如下:
visitor.visit(this);
如此一来, doIterator方法拜托了visitor的依赖. 那么,在doIterator方法中,有怎么样知道就是需要调用t.accept()方法呢?
一样地,
Interface的标准化.
这么一来,就形成了visitor模式:
结束语: 可以使用java Reflection的反射机制使得Viditor模式更加具有灵活性.
|