**
设计模式(十四)之访问者模式
**
-
案例说明
我们有一家水果小店,现在老板想要做一个店内水果的评价系统,以三种水果——苹果、香蕉、芒果为例,评价等级有Nice、bad、SoSo,如果我们的水果种类越来越多,评价等级也越来越多,我们的用传统写法来做的话代码维护量就会倍增,所以我们可以用访问者模式来解决这个需求。
访问者模式里面有几种角色——访问者、具体元素(行为、属性)、管理访问者和具体元素的接口(数据结构)。在外面的例子里面,水果是抽象的访问者,苹果、香蕉、芒果是具体的访问者,评价等级可以为抽象元素,Nice、bad、SoSo是具体的元素。先看一下类图:
Fruit中有一个抽象方法accept,参数接收Action;Apple、Banana、Mango继承了Fruit;Action包含了给Apple、Banana、Mango评价的三个抽象方法,也将Apple、Banana、Mango组合了进去;Nice、bad、SoSo继承了Action;ObjectStructure将Fruit和Action组合了进去,包含了一个集合用来添加、移除Fruit,还有选择味道评价的方法。
Fruit抽象类
public abstract class Fruit {
// 提供一个方法,让访问者可以访问
public abstract void accept(Action action);
}
Fruit的具体子类
public class Apple extends Fruit{
@Override
public void accept(Action action) {
action.getAppleTaste(this);
}
}
public class Banana extends Fruit{
@Override
public void accept(Action action) {
action.getBananaTaste(this);
}
}
public class Mango extends Fruit{
@Override
public void accept(Action action) {
action.getMangoTaste(this);
}
}
抽象评价
public abstract class Action {
// 评价苹果的味道
public abstract void getAppleTaste(Apple apple);
// 评价香蕉的味道
public abstract void getBananaTaste(Banana banana);
// 评价芒果的味道
public abstract void getMangoTaste(Mango mango);
}
具体的评价
public class Nice extends Action{
@Override
public void getAppleTaste(Apple apple) {
System.out.println("苹果很好吃");
}
@Override
public void getBananaTaste(Banana banana) {
System.out.println("香蕉很好吃");
}
@Override
public void getMangoTaste(Mango mango) {
System.out.println("芒果很好吃");
}
}
public class SoSo extends Action{
@Override
public void getAppleTaste(Apple apple) {
System.out.println("苹果味道一般般");
}
@Override
public void getBananaTaste(Banana banana) {
System.out.println("香蕉味道一般般");
}
@Override
public void getMangoTaste(Mango mango) {
System.out.println("芒果味道一般般");
}
}
public class Bad extends Action{
@Override
public void getAppleTaste(Apple apple) {
System.out.println("苹果不好吃");
}
@Override
public void getBananaTaste(Banana banana) {
System.out.println("香蕉不好吃");
}
@Override
public void getMangoTaste(Mango mango) {
System.out.println("芒果不好吃");
}
}
评价队列
public class ObjectStructure {
// 用来管理评价队列里面的水果
private List<Fruit> fruits = new LinkedList<>();
// 在评价队列里面添加一样水果
public void add(Fruit fruit){
fruits.add(fruit);
}
// 在评价队列里面移除一样水果
public void remove(Fruit fruit){
fruits.remove(fruit);
}
// 给评价队列里面的水果都选择同一种评价
public void choose(Action action){
for (Fruit fruit : fruits) {
fruit.accept(action);
}
}
}
客户端
public class Client {
public static void main(String[] args) {
// 拿到一个评价队列
ObjectStructure os = new ObjectStructure();
Apple apple = new Apple();
// 在评价队列中加入苹果
os.add(apple);
// 给评价队列中的苹果选择好吃的评价
os.choose(new Nice());
// 在评价队列中移除苹果
os.remove(apple);
// 在评价队列中加入香蕉
os.add(new Banana());
// 在评价队列中加入芒果
os.add(new Mango());
// 给评价队列中的香蕉和芒果选择一般般的评价
os.choose(new SoSo());
}
}
测试
D:\jdk8\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=56628:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\ideaworkspace\design_pattern\design\target\classes;D:\dev_tools\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar;D:\dev_tools\repository\cglib\cglib\3.3.0\cglib-3.3.0.jar;D:\dev_tools\repository\org\ow2\asm\asm\7.1\asm-7.1.jar com.wd.visitor.Client
苹果很好吃
香蕉味道一般般
芒果味道一般般
Process finished with exit code 0
- 总结
1、访问者模式符合单一职责原则、让程序具有优秀的扩展性、灵活性非常高;
2、访问者模式可以对功能进行统一,可以做报表、UI、拦截器与过滤器,适用于数据结构相对稳定的系统;
3、具体元素对访问者公布细节,也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的, 这样造成了具体元素变更比较困难;
4、违背了依赖倒转原则。访问者依赖的是具体元素,而不是抽象元素;
5、如果一个系统有比较稳定的数据结构,又有经常变化的功能需求,那么访问者模式就是比较合适的。