一、Visitor 模式--访问数据结构并处理数据
1. 场景
之前有做个根据文件目录结构访问的案例,也就是一致性一文中的 Composite 模式,现在我们用 “访问者” 模式
来试着做转换。
Visitor 模式:作者在引导语中是这样描述的:在数据结构中保存着许多元素,我们会对这些元素进行“处理”,这时,处理的代码应该放在哪里?通常的做法是放在表示数据结构的类中。但是,如果这样的处理很多呢?这种情况下,每当增加一种处理,我们就不得不修改表示数据结构的类。而在 Visitor 模式中,将数据结构与处理被分离出来,我们编写一个表示“访问者”的类来访问数据结构中的元素,并把对各个元素的处理交给访问者类。这样新的访问者对于原有的数据结构类没有破坏,而是新增一种访问者即可。
2. UML
3. Code
3.1 Visitor (为每种数据结构 file directory 定义一种访问方法)
/**
* @author CSZ
*/
public abstract class Visitor {
public abstract void visit(File file);
public abstract void visit(Directory directory);
}
3.2 Element (将标记访问对象,只要实现了这个类,就一定要提供接受一个Vistitor 的方法)
/**
* @author CSZ
*/
@FunctionalInterface
public interface Element {
void accept(Visitor v);
}
3.3 Entry (实现了 Element,也就是被标记要提供一个接受观察者的方法,但是延迟到子类实现)
/**
* @author CSZ
*/
public abstract class Entry implements Element{
public abstract String getName();
public abstract int getSize();
public Entry add(Entry entry) throws FileTreatmentException {
throw new FileTreatmentException();
}
public Iterator iterator() throws FileTreatmentException{
throw new FileTreatmentException();
}
@Override
public String toString() {
return getName() + " (" + getSize() + ")";
}
}
3.4 File 继承自 Entry 从而要求声明一个 accpet 方法
/**
* @author CSZ
*/
public class File extends 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;
}
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
3.5 Directory 与上文类似
/**
* @author CSZ
*/
public class Directory extends Entry {
private String name;
private List<Entry> directory = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
int size = 0;
Iterator<Entry> iterator = directory.iterator();
while (iterator.hasNext()){
size += iterator.next().getSize();
}
return size;
}
@Override
public Entry add(Entry entry){
directory.add(entry);
return this;
}
@Override
public Iterator<Entry> iterator(){
return directory.iterator();
}
@Override
public void accept(Visitor v) {
v.visit(this);
}
}
3.6 FileTreatmentException 自定义异常
/**
* @author CSZ
*/
public class FileTreatmentException extends RuntimeException{
public FileTreatmentException() {
}
public FileTreatmentException(String message) {
super(message);
}
}
3.7 ListVisitor (Visitor 提供了不同类型的访问方法,ListVisitor 针对其做具体实现)
/**
* @author CSZ
*/
public class ListVisitor extends Visitor{
// 表示当前正在访问的文件夹的名字
private String currentdir = "";
@Override
public void visit(File file) {
System.out.println(currentdir + "/" + file);
}
@Override
public void visit(Directory directory) {
System.out.println(currentdir + "/" + directory);
String savedir = currentdir;
currentdir = currentdir + "/" + directory.getName();
Iterator<Entry> iterator = directory.iterator();
while (iterator.hasNext()){
iterator.next().accept(this);
}
currentdir = savedir;
}
}
3.8 MainTest
/**
* @author CSZ
*/
public class MainTest {
public static void main(String[] args) {
try {
System.out.println("创建根目录");
Directory rootdir = new Directory("root");
Directory bindir = new Directory("bin");
Directory tmpdir = new Directory("tmp");
Directory usrdir = new Directory("usr");
rootdir.add(bindir);
rootdir.add(tmpdir);
rootdir.add(usrdir);
bindir.add(new File("vi",10000));
bindir.add(new File("latex",20000));
rootdir.accept(new ListVisitor());
System.out.println();
System.out.println("创建用户目录下");
Directory yuki = new Directory("yuki");
Directory hanakao = new Directory("hanakao");
Directory tomura = new Directory("tomura");
usrdir.add(yuki);
usrdir.add(hanakao);
usrdir.add(tomura);
yuki.add(new File("diary.html",100));
hanakao.add(new File("Composite.java",200));
rootdir.accept(new ListVisitor());
} catch (FileTreatmentException e){
e.printStackTrace();
}
}
}
4. 总结与思考
代码的逻辑稍微啰嗦,建议大家 通过 debug 的方式熟悉调用过程。
- 双重分发 ConcreteElement ConcreteVisitor 共同决定实际的处理操作。
Directoy 中 public void accept(Visitor v) { v.visit(Directory directory); }
MainTest 中 Directoy 实例 rootdir rootdir.accept(new ListVisitor()); - 设计的思想,大家还记的桥接模式么,我们通过一个连接的桥梁将类的功能实现和类的功能扩展来分层。这里也是类似的思想,由双重分发确定了最终的处理流程,但是再此之前,我们并没有以硬编码方式直接实现,而是将数据结构和处理分离。也体现了 设计模式中核心思想:开闭原则,对扩展开发,对修改关闭。
- 缺点,如果我们不想公开 iterator,或者 getName() 这样的信息,这种模式就无法使用。
二、Chain of Responsiblity 模式 -- 推卸责任
其实看到这个模型的介绍,第一印象是想到了 java 的异常机制中抓抛模型。
1. 场景
2. UML
3. Code
3.1 Trouble 用于生成问题
/**
* @author CSZ
*/
public class Trouble {
private int number;
public Trouble(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
@Override
public String toString() {
return "Trouble{" +
"number=" + number +
'}';
}
}
3.2 Support 顶级Support 抽象类
/**
* 实现的核心关键在于,责任链的产生和推进定义在顶级父类中
* 而真正的执行操作在每一个子类中 resolve
* @author CSZ
*/
public abstract class Support {
private String name;
// 自己包含自己
private Support next;
public Support(String name) {
this.name = name;
}
// 由此我们可以将责任推给下一个处理者
public Support setNext(Support next){
this.next = next;
// 根据我们定义的代码,可以构建我们的责任链,也体现了一致性原则
return next;
}
// 判断是否支持处理
public final void support(Trouble trouble){
// 通过 resolve 判断是否可以处理
if (resolve(trouble)){
// 进行处理
done(trouble);
// 如果处理不了
//判断是否还有其他处理器:类似单向链表,每个节点都保留了自己的信息,以及一下节点信息
}else if (next != null){
// 如果有在递归调用
next.support(trouble);
} else {
// 全部责任链节点处理失败
fail(trouble);
}
}
// 这里体现为定义接口 API 由子类实现
protected abstract boolean resolve(Trouble trouble);
protected void done(Trouble trouble){
System.out.println(trouble + "is resolved by" + this + ".");
}
protected void fail(Trouble trouble){
System.out.println(trouble + " can't be resolved.");
}
@Override
public String toString() {
return '[' + name + ']';
}
}
3.3 NoSupport 空处理
/**
* @author CSZ
*/
public class NoSupport extends Support{
public NoSupport(String name) {
super(name);
}
@Override
protected boolean resolve(Trouble trouble) {
return false;
}
}
3.4 奇数处理器
/**
* @author CSZ
*/
public class OddSupport extends Support{
public OddSupport(String name) {
super(name);
}
@Override
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() % 2 == 1){
return true;
} else {
return false;
}
}
}
3.5 LimitSupport 范围值处理器
/**
* @author CSZ
*/
public class LimitSupport extends Support{
private int limit;
public LimitSupport(String name, int limit) {
super(name);
this.limit = limit;
}
@Override
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() < limit){
return true;
}else {
return false;
}
}
}
3.6 SpecialSupport 特殊值处理器
/**
* @author CSZ
*/
public class SpecialSupport extends Support{
private int number;
public SpecialSupport(String name,int number) {
super(name);
this.number = number;
}
@Override
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() == number){
return true;
} else {
return false;
}
}
}
3.7 MainTest
/**
* @author CSZ
*/
public class MainTest {
public static void main(String[] args) {
Support alice = new NoSupport("Alice");
Support bob = new LimitSupport("Bob", 100);
Support charlie = new SpecialSupport("Charlie", 429);
Support diana = new LimitSupport("Diana", 200);
Support elmo = new OddSupport("Elmo");
Support fred = new LimitSupport("Fred", 300);
alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
for (int i = 0; i < 500; i++) {
alice.support(new Trouble(i));
}
}
}
4. 总结与思考
- 共同实现一个父类,并且重写一个抽象的方法,以实现责任下放,而且还保留了一致性
-
书中提出:这种模式的好处,是弱化了请求者和处理者之间的联系。我突然想起了 spring 中经典的 aop的原理,被成为面向方面编程。大家想:我们处理一件事情,类中的实现越具体,那么复用性就越差。
举例:
- 除此之外,我们还可以配置责任链的请求线路,比如:优先处理 get 的请求,因为 get 的占比大,可以在nosupport 不论什么请求,先进行一波日志收集。