个人随笔
目录
二十一、行为型-通过中间类-访问者(Visitor)模式
2020-09-09 21:47:57

现在我们来学习第三种设计模式行为型的第四类,通过中间类的第一种访问者模式,这个模式比较难理解,也比较少用,但是如果需要用就很方便了。下面我们一起来学习吧。

1、访问者模式

访问者模式是一种较为复杂的行为型模式,它包含访问者和被访问元素两个主要组成部分,这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作。

2、适用场景

比如图书馆有两种类型的书,图书和光盘,这个也就是元素,然后比如我们的访问者图书管理员主要是把书本放到书架,把光盘放到抽屉。读者主要是借阅图书和观看光盘。若是此时我们多一个财务,那么财务的目的是统计书本的金额和光盘的金额。如果不用访问者模式,可能我们会做如下操作:

  1. public class Book {
  2. public void visit(String visit) {
  3. if("图书馆管理员".equals(visit)) {
  4. System.out.println("把书放到书架上");
  5. }else if("读者".equals(visit)) {
  6. System.out.println("借阅图书");
  7. }
  8. }
  9. }

假如多一个财务,那么我们就必须打开Book类,然后添加一个判断。这样会违背对扩展开放,对修改关闭的设计原则,并且会导致这个类很臃肿。也许有读者会说看起来,不臃肿啊,那是因为我们的业务处理逻辑就打印一句话而已。
但是我们如果改为如下介绍的访问者模式,就不存在这个问题了,不管添加任何访问者,都不需要去修改元素的结构。不过访问者模式有一个前提,那就是:元素结构要相对稳定,否则就不要用访问者模式。访问者模式的优点是增加操作,也就是增加访问者很方便,但是修改数据结构很麻烦。
所以我觉得适用场景是:元素结构稳定,但是对元素的操作又经常会改变的系统。

3、UML

访问者模式结构图中包含以下5个角色:

  1. Visitor(抽象访问者):抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者则需要实现这些操作方法,定义对这些元素的访问操作。
  2. ConcreteVisitor(具体访问者):具体访问者实现了抽象访问者声明的方法,每一个操作作用于访问对象结构中一种类型的元素。
  3. Element(抽象元素):一般是一个抽象类或接口,定义一个Accept方法,该方法通常以一个抽象访问者作为参数。
  4. ConcreteElement(具体元素):具体元素实现了Accept方法,在Accept方法中调用访问者的访问方法以便完成一个元素的操作。
  5. ObjectStructure(对象结构):对象结构是一个元素的集合,用于存放元素对象,且提供便利其内部元素的方法。

由UML图我们很清楚的知道,在元素中用一个accept接收一个访问者对象,然后内部直接调用访问者对象访问该对象的方法,然后把该对象传送出去给访问者,这样子就巧妙的把业务逻辑的处理交给了每一个访问者。访问者对这些元素进行操作,但是不会对元素的内部结构进行任何修改,这个是访问者模式的精髓。
按我理解,这种模式说是通过中间类,那么按类图可以知道,中间类应该是ObjectStructure。它存放了要访问的所有元素对象集合,然后提供了访问者操作元素集合的接口。

4、例子

我们下面就举上面说过的一个例子,(管理员、读者)【访问者】去访问(书本和光盘)【元素】,管理员要对书本进行放到书架,对光盘放到抽屉,读者要借阅图书和观看光盘。后面还可能会增加财务来统计书本的采购金额以及光盘的采购金额,也还可能增加新的访问者比如收废品的去把没用的图书光盘进行收集。哈,可能我的例子不是很好,但是应该对理解这个模式很方便,元素很稳定就只有书和光盘,但是访问者一直变化。

5、例子UML


我们这里有三个访问者:图书馆管理员、读者、财务
两个元素:书本、光盘
中间类:ObjectStructure

6、源码如下

  1. /**
  2. * 抽象访问者
  3. * @author suibibk.com
  4. */
  5. public abstract class Visit {
  6. public abstract void visitBook(Book book);
  7. public abstract void visitCD(CD cd);
  8. }
  1. /**
  2. * 具体访问者:管理员
  3. * @author suibibk.com
  4. */
  5. public class Manager extends Visit{
  6. @Override
  7. public void visitBook(Book book) {
  8. System.out.println("图书上架:Book="+book.getName());
  9. }
  10. @Override
  11. public void visitCD(CD cd) {
  12. System.out.println("CD放抽屉:CD="+cd.getName());
  13. }
  14. }
  1. /**
  2. * 具体访问者:财务
  3. * @author suibibk.com
  4. */
  5. public class Finance extends Visit{
  6. @Override
  7. public void visitBook(Book book) {
  8. System.out.println("计算图书的价格:Book="+book.getName());
  9. }
  10. @Override
  11. public void visitCD(CD cd) {
  12. System.out.println("计算CD的价格:CD="+cd.getName());
  13. }
  14. }
  1. /**
  2. * 具体访问者:读者
  3. * @author suibibk.com
  4. */
  5. public class Reader extends Visit{
  6. @Override
  7. public void visitBook(Book book) {
  8. System.out.println("查阅图书:Book="+book.getName());
  9. }
  10. @Override
  11. public void visitCD(CD cd) {
  12. System.out.println("查阅CD:CD="+cd.getName());
  13. }
  14. }
  1. /**
  2. * 抽象元素
  3. * @author suibibk.com
  4. */
  5. public interface Element {
  6. public void accept(Visit visit);
  7. }
  1. /**
  2. * 具体元素:书本
  3. * @author suibibk.com
  4. */
  5. public class Book implements Element {
  6. private String name;
  7. public Book(String name) {
  8. this.name=name;
  9. }
  10. public String getName() {
  11. return name;
  12. }
  13. @Override
  14. public void accept(Visit visit) {
  15. visit.visitBook(this);
  16. }
  17. }
  1. /**
  2. * 具体元素:光盘
  3. * @author suibibk.com
  4. */
  5. public class CD implements Element {
  6. private String name;
  7. public CD(String name) {
  8. this.name=name;
  9. }
  10. public String getName() {
  11. return name;
  12. }
  13. @Override
  14. public void accept(Visit visit) {
  15. visit.visitCD(this);
  16. }
  17. }
  1. /**
  2. * 对象结构,中间类
  3. * @author suibibk.com
  4. */
  5. public class ObjectStructure {
  6. private List<Element> elements = new ArrayList<Element>();
  7. public void addElement(Element element) {
  8. if(!elements.contains(element)) {
  9. elements.add(element);
  10. }
  11. }
  12. public void visit(Visit visit) {
  13. for (Element element : elements) {
  14. element.accept(visit);
  15. }
  16. }
  17. }
  1. /**
  2. * 客户端
  3. * @author suibibk.com
  4. */
  5. public class Client {
  6. public static void main(String[] args) {
  7. ObjectStructure object = new ObjectStructure();
  8. Book book1 = new Book("英语书");
  9. Book book2 = new Book("数学书");
  10. Book book3 = new Book("语文书");
  11. CD cd1 = new CD("音乐光盘");
  12. CD cd2 = new CD("体育光盘");
  13. CD cd3 = new CD("影视光盘");
  14. object.addElement(book1);
  15. object.addElement(book2);
  16. object.addElement(book3);
  17. object.addElement(cd1);
  18. object.addElement(cd2);
  19. object.addElement(cd3);
  20. //管理员访问
  21. System.out.println("管理员访问");
  22. object.visit(new Manager());
  23. //读者访问
  24. System.out.println("读者访问");
  25. object.visit(new Reader());
  26. //财务访问
  27. System.out.println("财务访问");
  28. object.visit(new Finance());
  29. }
  30. }

运行客户端,输出结果如下:

  1. 管理员访问
  2. 图书上架:Book=英语书
  3. 图书上架:Book=数学书
  4. 图书上架:Book=语文书
  5. CD放抽屉:CD=音乐光盘
  6. CD放抽屉:CD=体育光盘
  7. CD放抽屉:CD=影视光盘
  8. 读者访问
  9. 查阅图书:Book=英语书
  10. 查阅图书:Book=数学书
  11. 查阅图书:Book=语文书
  12. 查阅CDCD=音乐光盘
  13. 查阅CDCD=体育光盘
  14. 查阅CDCD=影视光盘
  15. 财务访问
  16. 计算图书的价格:Book=英语书
  17. 计算图书的价格:Book=数学书
  18. 计算图书的价格:Book=语文书
  19. 计算CD的价格:CD=音乐光盘
  20. 计算CD的价格:CD=体育光盘
  21. 计算CD的价格:CD=影视光盘

到这里就实现了一个访问者模式的例子,如果我们要加访问者,只需要实现Visit就可以拥有对元素的新的操作而不用改变元素的对象。

7、优缺点

优点

  1. 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化。
  2. 添加新的操作或者说访问者会非常容易。
  3. 将对各个元素的一组操作集中在一个访问者类当中。
  4. 使得类层次结构不改变的情况下,可以针对各个层次做出不同的操作,而不影响类层次结构的完整性。
  5. 可以跨越类层次结构,访问不同层次的元素类,做出相应的操作。

缺点

  1. 增加新的元素会非常困难。
  2. 实现起来比较复杂,会增加系统的复杂性。
  3. 破坏封装,如果将访问行为放在各个元素中,则可以不暴露元素的内部结构和状态,但使用访问者模式的时候,为了让访问者能获取到所关心的信息,元素类不得不暴露出一些内部的状态和结构,就像收入和支出类必须提供访问金额和单子的项目的方法一样。

总结

访问者模式适用于元素稳定,操作比较多变化的情况,比较复杂,但是如果用的到就非常方便。根据上面的例子我们应该可以对这个模式有很好的理解。

 486

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2