访问数据结构

一、Visitor 模式--访问数据结构并处理数据

1. 场景

之前有做个根据文件目录结构访问的案例,也就是一致性一文中的 Composite 模式,现在我们用 “访问者” 模式
来试着做转换。
Visitor 模式:作者在引导语中是这样描述的:在数据结构中保存着许多元素,我们会对这些元素进行“处理”,这时,处理的代码应该放在哪里?通常的做法是放在表示数据结构的类中。但是,如果这样的处理很多呢?这种情况下,每当增加一种处理,我们就不得不修改表示数据结构的类。而在 Visitor 模式中,将数据结构与处理被分离出来,我们编写一个表示“访问者”的类来访问数据结构中的元素,并把对各个元素的处理交给访问者类。这样新的访问者对于原有的数据结构类没有破坏,而是新增一种访问者即可。

2. UML

image.png

3. Code

3.1 Visitor (为每种数据结构 file directory 定义一种访问方法)

/**
 * @author CSZ
 */
public abstract class Visitor {
    public abstract void visit(File file);
    public abstract void visit(Directory directory);
}

3.2 Element (将标记访问对象,只要实现了这个类,就一定要提供接受一个Vistitor 的方法)

/**
 * @author CSZ
 */
@FunctionalInterface
public interface Element {
    void accept(Visitor v);
}

3.3 Entry (实现了 Element,也就是被标记要提供一个接受观察者的方法,但是延迟到子类实现)

/**
 * @author CSZ
 */
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();
    }
    @Override
    public String toString() {
        return getName() + " (" + getSize() + ")";
    }
}

3.4 File 继承自 Entry 从而要求声明一个 accpet 方法

/**
 * @author CSZ
 */
public class File extends Entry {
    private String name;
    private int size;

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

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        return size;
    }

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

3.5 Directory 与上文类似

/**
 * @author CSZ
 */
public class Directory extends Entry {
    private String name;
    private List<Entry> directory = new ArrayList<>();

    public Directory(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        int size = 0;
        Iterator<Entry> iterator = directory.iterator();
        while (iterator.hasNext()){
            size += iterator.next().getSize();
        }
        return size;
    }

    @Override
    public Entry add(Entry entry){
        directory.add(entry);
        return this;
    }

    @Override
    public Iterator<Entry> iterator(){
        return directory.iterator();
    }

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

3.6 FileTreatmentException 自定义异常

/**
 * @author CSZ
 */
public class FileTreatmentException extends RuntimeException{
    public FileTreatmentException() {
    }

    public FileTreatmentException(String message) {
        super(message);
    }
}

3.7 ListVisitor (Visitor 提供了不同类型的访问方法,ListVisitor 针对其做具体实现)

/**
 * @author CSZ
 */
public class ListVisitor extends Visitor{

    // 表示当前正在访问的文件夹的名字
    private String currentdir = "";

    @Override
    public void visit(File file) {
        System.out.println(currentdir + "/" + file);
    }

    @Override
    public void visit(Directory directory) {
        System.out.println(currentdir + "/" + directory);
        String savedir = currentdir;
        currentdir = currentdir + "/" + directory.getName();
        Iterator<Entry> iterator = directory.iterator();
        while (iterator.hasNext()){
            iterator.next().accept(this);
        }
        currentdir = savedir;
    }
}

3.8 MainTest

/**
 * @author CSZ
 */
public class MainTest {

    public static void main(String[] args) {
        try {
            System.out.println("创建根目录");
            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("创建用户目录下");
            Directory yuki = new Directory("yuki");
            Directory hanakao = new Directory("hanakao");
            Directory tomura = new Directory("tomura");
            usrdir.add(yuki);
            usrdir.add(hanakao);
            usrdir.add(tomura);
            yuki.add(new File("diary.html",100));
            hanakao.add(new File("Composite.java",200));
            rootdir.accept(new ListVisitor());

        } catch (FileTreatmentException e){
            e.printStackTrace();
        }
    }
}
image.png
image.png

4. 总结与思考

代码的逻辑稍微啰嗦,建议大家 通过 debug 的方式熟悉调用过程。

  1. 双重分发 ConcreteElement ConcreteVisitor 共同决定实际的处理操作。
    Directoy 中 public void accept(Visitor v) { v.visit(Directory directory); }
    MainTest 中 Directoy 实例 rootdir rootdir.accept(new ListVisitor());
  2. 设计的思想,大家还记的桥接模式么,我们通过一个连接的桥梁将类的功能实现和类的功能扩展来分层。这里也是类似的思想,由双重分发确定了最终的处理流程,但是再此之前,我们并没有以硬编码方式直接实现,而是将数据结构和处理分离。也体现了 设计模式中核心思想:开闭原则,对扩展开发,对修改关闭。
  3. 缺点,如果我们不想公开 iterator,或者 getName() 这样的信息,这种模式就无法使用。


二、Chain of Responsiblity 模式 -- 推卸责任

其实看到这个模型的介绍,第一印象是想到了 java 的异常机制中抓抛模型。

1. 场景

image.png

2. UML

image.png

3. Code

image.png

3.1 Trouble 用于生成问题

/**
 * @author CSZ
 */
public class Trouble {
    
    private int number;

    public Trouble(int number) {
        this.number = number;
    }

    public int getNumber() {
        return number;
    }

    @Override
    public String toString() {
        return "Trouble{" +
                "number=" + number +
                '}';
    }
}

3.2 Support 顶级Support 抽象类

/**
 * 实现的核心关键在于,责任链的产生和推进定义在顶级父类中
 * 而真正的执行操作在每一个子类中 resolve
 * @author CSZ
 */
public abstract class Support {
    private String name;
    // 自己包含自己
    private Support next;
    public Support(String name) {
        this.name = name;
    }
    // 由此我们可以将责任推给下一个处理者
    public Support setNext(Support next){
        this.next = next;
        // 根据我们定义的代码,可以构建我们的责任链,也体现了一致性原则
        return next;
    }
    // 判断是否支持处理
    public final void support(Trouble trouble){
        // 通过 resolve 判断是否可以处理
        if (resolve(trouble)){
            // 进行处理
            done(trouble);
        // 如果处理不了
        //判断是否还有其他处理器:类似单向链表,每个节点都保留了自己的信息,以及一下节点信息
        }else if (next != null){
            // 如果有在递归调用
            next.support(trouble);
        } else {
            // 全部责任链节点处理失败
            fail(trouble);
        }
    }
    // 这里体现为定义接口 API 由子类实现
    protected abstract boolean resolve(Trouble trouble);

    protected void done(Trouble trouble){
        System.out.println(trouble + "is resolved by" + this + ".");
    }
    protected void fail(Trouble trouble){
        System.out.println(trouble + " can't be resolved.");
    }

    @Override
    public String toString() {
        return '[' + name + ']';
    }
}

3.3 NoSupport 空处理

/**
 * @author CSZ
 */
public class NoSupport extends Support{

    public NoSupport(String name) {
        super(name);
    }

    @Override
    protected boolean resolve(Trouble trouble) {
        return false;
    }
}

3.4 奇数处理器

/**
 * @author CSZ
 */
public class OddSupport extends Support{

    public OddSupport(String name) {
        super(name);
    }

    @Override
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() % 2 == 1){
            return true;
        } else {
            return false;
        }
    }
}

3.5 LimitSupport 范围值处理器

/**
 * @author CSZ
 */
public class LimitSupport extends Support{

    private int limit;

    public LimitSupport(String name, int limit) {
        super(name);
        this.limit = limit;
    }

    @Override
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() < limit){
            return true;
        }else {
            return false;
        }
    }
}

3.6 SpecialSupport 特殊值处理器

/**
 * @author CSZ
 */
public class SpecialSupport extends Support{

    private int number;

    public SpecialSupport(String name,int number) {
        super(name);
        this.number = number;
    }

    @Override
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() == number){
            return true;
        } else {
            return false;
        }
    }
}

3.7 MainTest

/**
 * @author CSZ
 */
public class MainTest {

    public static void main(String[] args) {
        Support alice = new NoSupport("Alice");
        Support bob = new LimitSupport("Bob", 100);
        Support charlie = new SpecialSupport("Charlie", 429);
        Support diana = new LimitSupport("Diana", 200);
        Support elmo = new OddSupport("Elmo");
        Support fred = new LimitSupport("Fred", 300);
        alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
        for (int i = 0; i < 500; i++) {
            alice.support(new Trouble(i));
        }
    }
}

4. 总结与思考

  1. 共同实现一个父类,并且重写一个抽象的方法,以实现责任下放,而且还保留了一致性
  2. 书中提出:这种模式的好处,是弱化了请求者和处理者之间的联系。我突然想起了 spring 中经典的 aop的原理,被成为面向方面编程。大家想:我们处理一件事情,类中的实现越具体,那么复用性就越差。
    举例:


    image.png
  3. 除此之外,我们还可以配置责任链的请求线路,比如:优先处理 get 的请求,因为 get 的占比大,可以在nosupport 不论什么请求,先进行一波日志收集。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • 1. 概述 通常当我们定义一个数据结构的时候,会觉得将对其进行处理的逻辑放在数据结构的类本身中是一件理所应当的事情...
    戴廿叁阅读 729评论 0 0
  • 目录:设计模式之小试牛刀源码路径:Github-Design Pattern 定义:(Visitor Patter...
    圣杰阅读 1,346评论 1 6
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,521评论 28 53
  • 人工智能是什么?什么是人工智能?人工智能是未来发展的必然趋势吗?以后人工智能技术真的能达到电影里机器人的智能水平吗...
    ZLLZ阅读 3,763评论 0 5
  • 首先介绍下自己的背景: 我11年左右入市到现在,也差不多有4年时间,看过一些关于股票投资的书籍,对于巴菲特等股神的...
    瞎投资阅读 5,660评论 3 8