0%

设计模式之Visitor模式

介绍

访问者模式能将算法与其所作用的对象隔离开来。在访问者模式(Visitor Pattern)中,使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作

  • 为什么还要有一个访问者模式呢?
    这就要放到OOP之中了,在面向对象编程的思想中,我们使用类来组织属性,以及对属性的操作,那么我们理所当然的将访问操作放到了类的内部,这样看起来没问题,但是当我们想要使用另一种遍历方式要怎么办呢,我们必须将这个类进行修改,这在设计模式中是大忌,在设计模式中就要保证,对扩展开放,对修改关闭的开闭原则。

所以我们考虑 可不可以将访问操作独立出来变成一个新的le我们需要增加访问操作的时候,直接增加新的类,原来的代码不需要任何的改变,如果可以这样做,那么我们的程序就是好的程序,因为可以扩展,符合开闭原则。而访问者模式就是实现这个的,使得使用不同的访问方式都可以对某些元素进行访问。

实现

Visitor类是表示访问者的抽象类
Visitor.java

1
2
3
4
5
6
7
package com.edu.tju.GOF.Visitor;

public abstract class Visitor {
public abstract void visit(File file);

public abstract void visit(Directory directory);
}

Element接口是接受访问者的访问男的接口

1
2
3
4
5
package com.edu.tju.GOF.Visitor;

public interface Element {
public abstract void accept(Visitor v);
}

Entry类再本质上 与 之前的Composite中的Entry类是一样的,不过这次实现了Element接口为了让Entry类适用于Visitor模式.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.edu.tju.GOF.Visitor;

import java.util.Iterator;

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 { // 生成Iterator
throw new FileTreatmentException();
}

public String toString() { // 显示字符串
return getName() + " (" + getSize() + ")";
}
}

File.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.edu.tju.GOF.Visitor;


public class File extends Entry {
private String name;
private int size;

public File(String name, int size) {
this.name = name;
this.size = size;
}

public String getName() {
return name;
}

public int getSize() {
return size;
}

public void accept(Visitor v) {
v.visit(this);
}
}

Directory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.edu.tju.GOF.Visitor;

import java.util.Iterator;
import java.util.ArrayList;

public class Directory extends Entry {
private String name; // 文件夹名字
private ArrayList dir = new ArrayList(); // 目录条目集合

public Directory(String name) { // 构造函数
this.name = name;
}

public String getName() { // 获取名字
return name;
}

public int getSize() { // 获取大小
int size = 0;
Iterator it = dir.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
size += entry.getSize();
}
return size;
}

public Entry add(Entry entry) { // 增加目录条目
dir.add(entry);
return this;
}

public Iterator iterator() { // 生成Iterator
return dir.iterator();
}

public void accept(Visitor v) { // 接受访问者的访问
v.visit(this);
}
}

ListVistor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.edu.tju.GOF.Visitor;

import java.util.Iterator;

public class ListVisitor extends Visitor {
private String currentdir = ""; // 当前访问的文件夹的名字

public void visit(File file) { // 在访问文件时被调用
System.out.println(currentdir + "/" + file);
}

public void visit(Directory directory) { // 在访问文件夹时被调用
System.out.println(currentdir + "/" + directory);
String savedir = currentdir;
currentdir = currentdir + "/" + directory.getName();
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
entry.accept(this);
}
currentdir = savedir;
}
}

FileVisitor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.edu.tju.GOF.Visitor;

import java.util.ArrayList;
import java.util.Iterator;

public class FileVisitor extends Visitor {

String currentDir = "";
String suffix;
// ArrayList files = new ArrayList();

public FileVisitor(String suffix) {
this.suffix = suffix;
}

public void visit(File file) {
if (file.getName().endsWith(suffix)) {
// System.out.println(currentDir+"/"+file);
System.out.println(currentDir + "/" + file);
// files.add(currentDir + "/" + file);

}
}

public void visit(Directory directory) {
String saveDir = currentDir;
currentDir += ("/" + directory.getName());
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
entry.accept(this);
}
currentDir = saveDir;
}

// Iterator getFiles() {
// return files.iterator();
// }

}

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.edu.tju.GOF.Visitor;

import java.util.Iterator;

public class Main {

public static void main(String[] args) {

Directory root = new Directory("根目录");

Directory life = new Directory("我的生活");
File eat = new File("吃火锅.txt", 100);
File sleep = new File("睡觉.html", 100);
File study = new File("学习.txt", 100);
life.add(eat);
life.add(sleep);
life.add(study);

Directory work = new Directory("我的工作");
File write = new File("写博客.doc", 200);
File paper = new File("写论文.html", 200);
File homework = new File("写家庭作业.docx", 200);
work.add(write);
work.add(paper);
work.add(homework);

Directory relax = new Directory("我的休闲");
File music = new File("听听音乐.js", 200);
File walk = new File("出去转转.psd", 200);
relax.add(music);
relax.add(walk);

Directory read = new Directory("我的阅读");
File book = new File("学习书籍.psd", 200);
File novel = new File("娱乐小说.txt", 200);
read.add(book);
read.add(novel);

root.add(life);
root.add(work);
root.add(relax);
root.add(read);

root.accept(new ListVisitor());
System.out.println("========================");
FileVisitor visitor = new FileVisitor(".psd");
root.accept(visitor);
// Iterator it = visitor.getFiles();
// while (it.hasNext()) {
// System.out.println(it.next());
// }

}

}

总结

访问者模式是一个非常有意思的模式,因为自己需要得到数据就需要向被访者索取,如果能够一次索取成功,访问就结束了,如果还需要其他信息,则再次向被访问者索取,就这样知道拿到自己需要的所有数据。在本例中借用了组合模式中的数据结构,那是因为这种树形的结构很适合我们进行递归访问。访问者模式和迭代器模式都是在某种数据结构上进行处理,一种是对数据结构中的元素进行某种特定的处理,另一种是用某种方式遍历所有元素。在实际应用中,我们根据实际需要来考虑是不是需要双重分发机制。在本例中的访问者模式中用到了组合模式、委托(组合)、双重分发等原理,便于新增访问方式,不便于对数据结构的修改。

Visitor模式 登场的橘色

  • Visitor(访问者,抽象类)
  • ConcreteVisitor(具体的访问者,实现visit接口)
  • Element(访问对象、抽象类)
  • ConcreteElement(具体的访问对象)
  • ObjectStructure(对象结构,遍历Element,处理Element角色的集合)

拓展

  • 双重分发机制
    在Visitor模式中方法的调用关系
    accept(接受)发给发的调用方式如下

    element.accept(visitor);

在visit(访问)方法的调用方式如下:

visitor.visit(element);

对比这两个方法,他们是相反的关系,element接受visitor,而visitor又访问element.

思考题

  1. 在示例程序中,Directory类的gitSize方法的作用是获取文件夹大小,请编写一个获取大小的SizeVisitor类,用它替换掉Directory类的getSize方法.
Thank you for your support