测评系统需求:将观众分为男人和女人,对歌手进行测评,当看完某个歌手表演后,得到他们对该歌手不同的评价(比如 成功、失败 等)
Man和Woman里面都有“成功”、“失败”的方法
【分析】
【改进】
使用访问者模式
数据结构
与数据操作
分离,解决数据结构和操作耦合性问题在被访问的类里面提供一个对外接待访问者的接口
Visitor
:是抽象访问者,为该对象结构中的ConcreteElement的每一个类声明一个visit方法ConcreteVisitor
:是一个具体的访问者,实现Visitor声明的每个方法ObjectStructure
:能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问元素(比如案例一的ObjectStructure类的display方法)Element
:定义一个accept 方法,接收一个访问者对象ConcreteElement
:为具体元素,实现了accept 方法Visitor(访问者)
:Visitor角色负责对数据结构中每个具体的元素(ConcreteElement角色)声明一个用于访问XXXXX的visit(XXXXX)方法。visit(XXXXX)是用于处理XXXXX的方法负责实现该方法的是ConcreteVisitor角色ConcreteVisitor(具体的访问者)
:ConcreteVisitor角色负责实现 Visitor角色所定义的接口(API)。它要实现所有的visit(XXXXX)方法,即实现如何处理每个ConcreteElement角色Element(元素)
:Element角色表示Visitor角色的访问对象。它声明了接受访问者的accept方法。accept 方法接收到的参数是Visitor角色ConcreteElement(具体元素)
:ConcreteElement角色负责实现Element角色所定义的接口(API)ObjectStructure(对象数据结构)
:ObjectStructur角色负责处理Element角色的集合,能够枚举它的元素(案例二的Directory类同时扮演该角色和ConcreteElement角色)【Action(Visitor)】
package com.test.visitor;
public abstract class Action {
/**
* 得到男性 的测评
* @param man
*/
public abstract void getManResult(Man man);
/**
* 得到女性 的测评
* @param woman
*/
public abstract void getWomanResult(Woman woman);
}
【Success(ConcreteVisitor)】
package com.test.visitor;
public class Success extends Action {
@Override
public void getManResult(Man man) {
System.out.println(" 男人给的评价该歌手很成功 !");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println(" 女人给的评价该歌手很成功 !");
}
}
【Fail(ConcreteVisitor)】
package com.test.visitor;
public class Fail extends Action {
@Override
public void getManResult(Man man) {
System.out.println(" 男人给的评价该歌手失败 !");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println(" 女人给的评价该歌手失败 !");
}
}
【Person(Element)】
package com.test.visitor;
public abstract class Person {
/**
* 提供一个方法,让访问者可以访问
*
* @param action
*/
public abstract void accept(Action action);
}
【Woman(ConcreteElement )】
package com.test.visitor;
/**
* 这里我们使用到了双分派, 即首先在客户端程序中,将具体状态作为参数传递到Woman中(第一次分派)
* 然后 Woman 类调用作为参数的 "具体方法" 中方法getWomanResult, 同时将自己(this)作为参数传入,完成第二次的分派
* 即互为对方方法的参数
*/
public class Woman extends Person{
@Override
public void accept(Action action) {
action.getWomanResult(this);
}
}
【Man(ConcreteElement )】
package com.test.visitor;
public class Man extends Person {
@Override
public void accept(Action action) {
// 自己认为是什么结果就是什么结果
action.getManResult(this);
}
}
【ObjectStructure】
package com.test.visitor;
import java.util.LinkedList;
import java.util.List;
/**
* 数据结构,管理很多人(Man , Woman)
*/
public class ObjectStructure {
/**
* 维护了一个集合
*/
private List<Person> persons = new LinkedList<>();
/**
* 将元素增加到list,在访问者模式中,一般使用attach,不使用add
*
* @param p
*/
public void attach(Person p) {
persons.add(p);
}
/**
* 移除
*
* @param p
*/
public void detach(Person p) {
persons.remove(p);
}
/**
* 显示测评情况
* @param action
*/
public void display(Action action) {
for (Person p : persons) {
p.accept(action);
}
}
}
【客户端】
package com.test.visitor;
public class Client {
public static void main(String[] args) {
//创建ObjectStructure
System.out.println("=======添加观众========");
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new Man());
objectStructure.attach(new Woman());
//成功
System.out.println("=======测评结果是成功晋级========");
Success success = new Success();
objectStructure.display(success);
System.out.println("=======测评结果是失败========");
Fail fail = new Fail();
objectStructure.display(fail);
}
}
【运行】
=======添加观众========
=======测评结果是成功晋级========
男人给的评价该歌手很成功 !
女人给的评价该歌手很成功 !
=======测评结果是失败========
男人给的评价该歌手失败 !
女人给的评价该歌手失败 !
Process finished with exit code 0
上面的程序使用了双重分发
,所谓双重分发
是指不管类怎么变化,我们都能找到期望的方法运行。双重分发
意味着得到执行的操作取决于请求的种类和两个接收者的类型。假设我们要添加一个Wait的状态类,考察Man类和Woman类的反应,由于使用了双重分发
,只需增加一个Action子类即可在客户端调用即可,不需要改动任何其他类的代码
【增加类:Wait】
package com.test.visitor;
public class Wait extends Action {
@Override
public void getManResult(Man man) {
System.out.println(" 男人给的评价是该歌手待定 ..");
}
@Override
public void getWomanResult(Woman woman) {
System.out.println(" 女人给的评价是该歌手待定 ..");
}
}
【客户端】
package com.test.visitor;
public class Client {
public static void main(String[] args) {
//创建ObjectStructure
System.out.println("=======添加观众========");
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new Man());
objectStructure.attach(new Woman());
System.out.println("=======测评结果是待定========");
Wait wait = new Wait();
objectStructure.display(wait);
}
}
【运行】
=======添加观众========
=======测评结果是待定========
男人给的评价是该歌手待定 ..
女人给的评价是该歌手待定 ..
Process finished with exit code 0
【访问者抽象类】
package com.test.visitor.Sample;
/**
* 访问者抽象类
* 依赖要访问的数据结构,File和Directory
*/
public abstract class Visitor {
/**
* 访问File类的方法
*
* @param file
*/
public abstract void visit(File file);
/**
* 访问Directory类的方法
*
* @param directory
*/
public abstract void visit(Directory directory);
}
【接受访问的接口】
package com.test.visitor.Sample;
/**
* 接受访问的接口
*/
public interface Element {
/**
* 接受访问
*
* @param v
*/
public abstract void accept(Visitor v);
}
【接受访问的抽象类】
这个类不需要实现accept方法,因为不是最终被访问的类
package com.test.visitor.Sample;
import java.util.Iterator;
public abstract class Entry implements Element {
/**
* 获取名字
*
* @return
*/
public abstract String getName();
/**
* 获取大小
*
* @return
*/
public abstract int getSize();
/**
* 增加目录条目
*
* @param entry
* @return
* @throws FileTreatmentException
*/
public Entry add(Entry entry) throws FileTreatmentException {
// 对 Directory 才有效,这里先简单报个错,让它自己重写一遍
throw new FileTreatmentException();
}
/**
* 生成Iterator
*
* @return
* @throws FileTreatmentException
*/
public Iterator iterator() throws FileTreatmentException {
// 对 Directory 才有效,这里先简单报个错,让它自己重写一遍
throw new FileTreatmentException();
}
/**
* 显示字符串
*
* @return
*/
public String toString() {
return getName() + " (" + getSize() + ")";
}
}
【异常类】
package com.test.visitor.Sample;
public class FileTreatmentException extends RuntimeException {
public FileTreatmentException() {
}
public FileTreatmentException(String msg) {
super(msg);
}
}
【接受访问的具体类:File(ConcreteElement角色)】
package com.test.visitor.Sample;
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(ConcreteElement角色、ObjectStructure角色)】
package com.test.visitor.Sample;
import java.util.ArrayList;
import java.util.Iterator;
public class Directory extends Entry {
/**
* 文件夹名字
*/
private String name;
/**
* 目录条目集合
*/
private ArrayList dir = new ArrayList();
/**
* 构造函数
* @param name
*/
public Directory(String name) {
this.name = name;
}
/**
* 获取名字
* @return
*/
public String getName() {
return name;
}
/**
* 获取大小
* @return
*/
public int getSize() {
int size = 0;
Iterator it = dir.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
size += entry.getSize();
}
return size;
}
/**
* 增加目录条目
* @param entry
* @return
*/
public Entry add(Entry entry) {
dir.add(entry);
return this;
}
/**
* 生成Iterator
* @return
*/
public Iterator iterator() {
return dir.iterator();
}
/**
* 接受访问者的访问
* @param v
*/
public void accept(Visitor v) {
v.visit(this);
}
}
【具体访问者】
package com.test.visitor.Sample;
import java.util.Iterator;
public class ListVisitor extends Visitor {
/**
* 当前访问的文件夹的名字
*/
private String currentdir = "";
/**
* 在访问文件时被调用
* 访问的是文件,就简单输出一下,文件的信息
* @param file
*/
public void visit(File file) {
System.out.println(currentdir + "/" + file);
}
/**
* 在访问文件夹时被调用
* 访问的是文件夹,不仅输出文件夹的信息,还要递归输出子文件和子文件夹的相关信息
* @param directory
*/
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;
}
}
【主类】
package com.test.visitor.Sample;
public class Main {
public static void main(String[] args) {
try {
System.out.println("Making root entries...");
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("Making user entries...");
Directory yuki = new Directory("yuki");
Directory hanako = new Directory("hanako");
Directory tomura = new Directory("tomura");
usrdir.add(yuki);
usrdir.add(hanako);
usrdir.add(tomura);
yuki.add(new File("diary.html", 100));
yuki.add(new File("Composite.java", 200));
hanako.add(new File("memo.tex", 300));
tomura.add(new File("game.doc", 400));
tomura.add(new File("junk.mail", 500));
// 接受访问,打印整个根目录下面的所有文件信息
rootdir.accept(new ListVisitor());
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}
【运行】
Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)
Making user entries...
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/Composite.java (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)
Process finished with exit code 0
在示例程序中增加一个FileFindvistor类,用于将带有指定后缀名的文件手机起来,存储到集合中
【FileFindvistor】
package com.test.visitor.A1;
import java.util.ArrayList;
import java.util.Iterator;
public class FileFindVisitor extends Visitor {
private String filetype;
private ArrayList found = new ArrayList();
/**
* 指定.后面的文件后缀名,如".txt"
*
* @param filetype
*/
public FileFindVisitor(String filetype) {
this.filetype = filetype;
}
/**
* 获取已经找到的文件
*
* @return
*/
public Iterator getFoundFiles() {
return found.iterator();
}
/**
* 在访问文件时被调用
*
* @param file
*/
public void visit(File file) {
if (file.getName().endsWith(filetype)) {
// 将符合格式的文件,添加到集合中
found.add(file);
}
}
/**
* 在访问文件夹时被调用
*
* @param directory
*/
public void visit(Directory directory) {
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
entry.accept(this);
}
}
}
【主类】
package com.test.visitor.A1;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
try {
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));
Directory yuki = new Directory("yuki");
Directory hanako = new Directory("hanako");
Directory tomura = new Directory("tomura");
usrdir.add(yuki);
usrdir.add(hanako);
usrdir.add(tomura);
yuki.add(new File("diary.html", 100));
yuki.add(new File("Composite.java", 200));
hanako.add(new File("memo.tex", 300));
hanako.add(new File("index.html", 350));
tomura.add(new File("game.doc", 400));
tomura.add(new File("junk.mail", 500));
// 筛选出.html结尾的文件
FileFindVisitor ffv = new FileFindVisitor(".html");
rootdir.accept(ffv);
// 输出.html结尾的文件
System.out.println("HTML files are:");
Iterator it = ffv.getFoundFiles();
while (it.hasNext()) {
File file = (File)it.next();
System.out.println(file.toString());
}
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}
【运行】
HTML files are:
diary.html (100)
index.html (350)
Process finished with exit code 0
Directory类的getSize方法的作用是获取文件夹大小,请编写一个获取大小的SizeVisitor类,用它替换掉 Directory类的getSize方法
【SizeVisitor】
import java.util.Iterator;
public class SizeVisitor extends Visitor {
private int size = 0;
public int getSize() {
return size;
}
public void visit(File file) {
size += file.getSize();
}
public void visit(Directory directory) {
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
entry.accept(this);
}
}
}
【修改Directory的方法】
package com.test.visitor.A2;
import java.util.ArrayList;
import java.util.Iterator;
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() {
// 使用visitor来替换原来的方式
SizeVisitor v = new SizeVisitor();
accept(v);
return v.getSize();
}
public Entry add(Entry entry) {
dir.add(entry);
return this;
}
public Iterator iterator() {
return dir.iterator();
}
public void accept(Visitor v) {
v.visit(this);
}
}
基于java.util.ArrayList类编写一个具有Element接口的ElementArrayList类,使得Directory类和File类可以被add至ElementArrayList 中,而且它还可以接受(accept) ListVisitor 的实例访问它
【ElementArrayList】
package com.test.visitor.A3;
import java.util.ArrayList;
import java.util.Iterator;
/**
* 继承ArrayList,这样就不用定义集合的add remove等操作
*/
class ElementArrayList extends ArrayList implements Element {
public void accept(Visitor v) {
// 使用迭代器遍历
Iterator it = iterator();
while (it.hasNext()) {
Element e = (Element)it.next();
e.accept(v);
}
}
}
由于visit方法不用传入ElementArrayList类作为参数,因此不用修改Visitor
【主类】
package com.test.visitor.A3;
public class Main {
public static void main(String[] args) {
try {
Directory root1 = new Directory("root1");
root1.add(new File("diary.html", 10));
root1.add(new File("index.html", 20));
Directory root2 = new Directory("root2");
root2.add(new File("diary.html", 1000));
root2.add(new File("index.html", 2000));
ElementArrayList list = new ElementArrayList();
list.add(root1);
list.add(root2);
list.add(new File("etc.html", 1234));
list.accept(new ListVisitor());
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}
【运行】
/root1 (30)
/root1/diary.html (10)
/root1/index.html (20)
/root2 (3000)
/root2/diary.html (1000)
/root2/index.html (2000)
/etc.html (1234)
Process finished with exit code 0
123456789
【优点】
【缺点】
// accept (接受)方法的调用方式
element.accept(visitor);
// visit(访问)方法的调用方式
visitor.visit(element);
ConcreteElement
和ConcreteVisitor
这两个角色互相调用共同决定了实际进行的处理