• 《图解设计模式》读书笔记6-1 VISITOR模式


    1. Visitor模式简介

    ​ Visitor是访问者的意思。

    ​ 一个数据结构包含多种数据元素,这些数据元素方便存储,却不一定方便使用。因此我们有必要架起一座桥梁,即对数据元素进行处理,方便别人使用。问题来了:数据的处理是放在数据结构类里面还是再造一个类专门用来处理?

    ​ 如果一个数据结构需要多种处理方式,那每增加一种就要修改数据结构的类,显然不合适。访问者模式就是解决这个问题来的。在这个模式中,数据结构和处理被分离开。我们编写一个访问者,即专门处理数据元素的类来处理数据元素,一旦有新的处理方式,就编写新的访问者类去访问数据结构即可。

    2. 示例

    接下来的例子以之前的Composite模式为基础编写。为File和Directory类添加一个accept方法,这个方法接收Visitor类,即访问者类。通过这个访问者我们能获得File和Directory类的经过处理的数据展示。

    2.1 类图

    2.2 代码

    ListVisitor里面的visit(Directory directory)使用了递归,值得好好体会一下。

    public abstract class Visitor {
        public abstract void visit(File file);
        public abstract void visit(Directory directory);
    }
    
    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;
        }
    }
    
    public interface Element {
        public abstract void accept(Visitor v);
    }
    
    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();
        }
        public String toString() {                                          // 显示字符串
            return getName() + " (" + getSize() + ")";
        }
    }
    
    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);
        }
    }
    
    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);
        }
    }
    
    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 xiaoming = new Directory("小明");
                Directory xiaohong = new Directory("小红");
                Directory xiaohua = new Directory("小花");
                usrdir.add(xiaoming);
                usrdir.add(xiaohong);
                usrdir.add(xiaohua);
                xiaoming.add(new File("diary.html", 100));
                xiaoming.add(new File("Composite.java", 200));
                xiaohong.add(new File("memo.tex", 300));
                xiaohua.add(new File("game.doc", 400));
                xiaohua.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/小明 (300)
    /root/usr/小明/diary.html (100)
    /root/usr/小明/Composite.java (200)
    /root/usr/小红 (300)
    /root/usr/小红/memo.tex (300)
    /root/usr/小花 (900)
    /root/usr/小花/game.doc (400)
    /root/usr/小花/junk.mail (500)
    

    3. 模式的角色和类图

    • Visitor(访问者): 抽象的访问者类,里面声明了供Element调用的访问方法visit。本例中由Visitor类扮演此角色。
    • ConcreteVisitor(具体的访问者):具体的访问者类,里面实现了供Element调用的访问方法visit,本例中由ListVisitor类扮演此角色。
    • Element(元素):表示Visitor访问的对象,声明了接收访问者的accept方法,参数是Visitor角色,本例中由Element接口扮演此角色。
    • ConcreteElement(具体的元素):实现Element定义的接口,本例中由File类和Directory类扮演此角色。
    • ObjectStructure(对象结构):负责处理Element角色的集合。ConcreteVisitor角色为每个Element角色都准备了处理方法,在本例中,由Directory类扮演此角色。为了让ConcreteVisitor可以遍历处理每个Element角色,在示例程序中,我们在Directory类中实现了iterator方法。

    4. 思路拓展

    4.1 双重分发

    在代码中,元素接受访问者:element.accept(visitor),访问者访问元素visitor.visit(element),对比这两个方法,他们是相反的关系,这种消息分发的方式被称为双重分发。

    4.2 开闭原则

    • 对扩展开放

    • 对修改关闭

    如果要增加功能,要在不修改现有代码的前提下进行扩展。Visitor模式中,如果要扩展数据结构的访问方法,不需要修改数据结构的类, 只需要增加一个访问者类即可。体现了开闭原则。

    4.3 难以增加ConcreteElement角色

    ​ Visitor模式很容易增加ConcreteVisitor角色,却很难应对ConcreteElement角色的增加。假设我们要增加Entry类的子类Device类,这个类和File类、Directory类是兄弟关系。我们就需要在Visitor类中声明一个visit(Device)方法,所有Visitor类的子类都要实现这个方法。

    4.4 Visitor工作所需的条件

    ​ Visitor需要从数据结构中获取到足够多的信息才能够工作。如果数据结构向Visitor公开了不该公开的信息,将来对数据结构的改良就会变得非常困难。

  • 相关阅读:
    OO第四单元总结
    OO第三单元总结
    OO第二单元总结
    OO第一单元总结
    面向对象第四单元总结
    面向对象第三单元总结
    面向对象第二单元的总结
    操作系统lab3实验总结
    操作系统lab2实验总结——Part2
    操作系统lab2实验总结——Part1
  • 原文地址:https://www.cnblogs.com/qianbixin/p/10941650.html
Copyright © 2020-2023  润新知