墨风如雪博客

  • 源码小店
  • 导航站
  • 登录
  • java
  • 资源分享
让AI使用变得如此简单
  1. 首页
  2. java
  3. 设计模式
  4. 正文

设计模式:访问者设计模式

2023年 6月 15日 119点热度 0人点赞 0条评论

一、概述

1.1 定义

访问者设计模式是一种行为型设计模式,用于将算法与对象结构分离。它允许你在不改变对象结构的前提下定义新的操作。

1.2 作用

访问者模式的作用是在不改变对象结构的前提下定义新的操作。它允许你定义一个新的操作,而无需修改现有的对象结构。在访问者模式中,我们将操作封装在访问者对象中,并在元素对象上调用访问者对象的方法,从而实现对元素对象的操作。

1.3 适用场景

访问者模式适用于以下场景:

  • 对象结构稳定,但是经常需要在此结构上定义新的操作;
  • 需要对复杂对象结构中的对象进行操作,而且这些对象可能具有不同的类型;
  • 需要在不改变对象结构的前提下,为对象结构中的元素对象动态添加新的操作。

二、角色

2.1 抽象访问者(Visitor)

抽象访问者(Visitor)定义了访问者可以访问的元素对象的接口。它包含了多个 visit() 方法,每个方法对应一个具体元素对象。

public interface Visitor {
    void visit(ConcreteElementA elementA);
    void visit(ConcreteElementB elementB);
}

在上述代码中,我们定义了一个抽象访问者接口 Visitor,它包含了两个 visit() 方法,分别对应具体元素对象 ConcreteElementA 和 ConcreteElementB。

2.2 具体访问者(ConcreteVisitor)

具体访问者(ConcreteVisitor)实现了抽象访问者接口,对不同类型的元素对象进行具体的操作。

public class ConcreteVisitorA implements Visitor {
    @Override
    public void visit(ConcreteElementA elementA) {
        System.out.println("ConcreteVisitorA visit ConcreteElementA");
    }

    @Override
    public void visit(ConcreteElementB elementB) {
        System.out.println("ConcreteVisitorA visit ConcreteElementB");
    }
}

public class ConcreteVisitorB implements Visitor {
    @Override
    public void visit(ConcreteElementA elementA) {
        System.out.println("ConcreteVisitorB visit ConcreteElementA");
    }

    @Override    public void visit(ConcreteElementB elementB) {
        System.out.println("ConcreteVisitorB visit ConcreteElementB");
    }
}

在上述代码中,我们定义了两个具体访问者类 ConcreteVisitorA 和 ConcreteVisitorB,它们分别实现了抽象访问者接口 Visitor,并对不同类型的元素对象进行具体的操作。

2.3 抽象元素(Element)

抽象元素(Element)定义了元素对象的接口,让访问者对象可以访问自己的元素对象。

public interface Element {
    void accept(Visitor visitor);
}

在上述代码中,我们定义了一个抽象元素接口 Element,它包含了一个 accept() 方法,该方法接收一个访问者对象作为参数。

2.4 具体元素(ConcreteElement)

具体元素(ConcreteElement)实现了抽象元素接口,定义了自己的 accept() 方法,该方法调用访问者对象的 visit() 方法,并将自身作为参数传入。

public class ConcreteElementA implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

public class ConcreteElementB implements Element {
@Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

在上述代码中,我们定义了两个具体元素类 ConcreteElementA 和 ConcreteElementB,它们分别实现了抽象元素接口 Element,并在 accept() 方法中调用访问者对象的 visit() 方法,并将自身作为参数传入。

2.5 对象结构(Object Structure)

对象结构(Object Structure)是元素对象的集合,它提供了一个接口,让访问者对象可以访问集合中的元素对象。

public class ObjectStructure {
    private List<Element> elements = new ArrayList<>();

    public void attach(Element element) {
        elements.add(element);
    }

    public void detach(Element element) {
        elements.remove(element);
    }

    public void accept(Visitor visitor) {
        for (Element element : elements) {
            element.accept(visitor);
        }
    }
}

在上述代码中,我们定义了一个对象结构类 ObjectStructure,它包含了一个元素对象的集合 elements,提供了 attach() 和 detach() 方法,用于添加和删除元素对象。它还提供了一个 accept() 方法,该方法遍历元素对象集合,并调用每个元素对象的accept() 方法,将访问者对象作为参数传入。

三、实现步骤

3.1 创建抽象元素类

public interface Element {
    void accept(Visitor visitor);
}

3.2 创建具体元素类

public class ConcreteElementA implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

public class ConcreteElementB implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

3.3 创建抽象访问者类

public interface Visitor {
    void visit(ConcreteElementA elementA);
    void visit(ConcreteElementB elementB);
}

3.4 创建具体访问者类

public class ConcreteVisitorA implements Visitor {
    @Override
    public void visit(ConcreteElementA elementA) {
        System.out.println("ConcreteVisitorA visit ConcreteElementA");
    }

    @Override
    public void visit(ConcreteElementB elementB) {
        System.out.println("ConcreteVisitorA visit ConcreteElementB");
    }
}

public class ConcreteVisitorB implements Visitor {
    @Override
    public void visit(ConcreteElementA elementA) {
        System.out.println("ConcreteVisitorB visit ConcreteElementA");
    }

    @Override
    publicvoid visit(ConcreteElementB elementB) {
        System.out.println("ConcreteVisitorB visit ConcreteElementB");
    }
}

3.5 创建对象结构类

public class ObjectStructure {
    private List<Element> elements = new ArrayList<>();

    public void attach(Element element) {
        elements.add(element);
    }

    public void detach(Element element) {
        elements.remove(element);
    }

    public void accept(Visitor visitor) {
        for (Element element : elements) {
            element.accept(visitor);
        }
    }
}

3.6 客户端调用

public class Client {
    public static void main(String[] args) {
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.attach(new ConcreteElementA());
        objectStructure.attach(new ConcreteElementB());

        Visitor visitorA = new ConcreteVisitorA();
        Visitor visitorB = new ConcreteVisitorB();

        objectStructure.accept(visitorA);
        objectStructure.accept(visitorB);
    }
}

在上述代码中,我们创建了一个对象结构 objectStructure,并向其中添加了两个元素对象 ConcreteElementA 和 ConcreteElementB。然后,我们创建了两个具体访问者对象 visitorA 和 visitorB,并调用 objectStructure 的 accept() 方法,将这两个访问者对象作为参数传入。在 accept() 方法中,我们遍历元素对象集合 elements,并依次调用每个元素对象的 accept() 方法,将访问者对象作为参数传入。

四、优缺点

4.1 优点

访问者设计模式的优点包括:

  • 可以在不改变对象结构的前提下定义新的操作;
  • 可以将代码的稳定性和易于扩展性相结合;
  • 可以使得增加新的操作变得简单。

4.2 缺点

访问者设计模式的缺点包括:

  • 增加新的元素对象比较困难;
  • 元素对象的访问者接口必须稳定,否则会导致所有访问者对象都需要进行修改;
  • 具体元素对象对访问者对象的访问是单向的,访问者对象无法访问元素对象的其他方法。

五、扩展点

5.1 双重分派

在访问者设计模式中,我们可以通过双重分派来实现不同的操作。双重分派是指在访问者对象中定义了多个具体的 visit() 方法,每个方法对应一个具体元素对象,然后在元素对象中调用访问者对象的 visit() 方法,并将自身作为参数传入。

双重分派的实现方式是通过重载 accept() 方法来实现的。具体来说,我们在抽象元素接口 Element 中定义多个 accept() 方法,每个方法对应一个具体访问者对象,并在具体元素对象中重载这些 accept() 方法,将具体访问者对象作为参数传入。

下面是一个双重分派的示例代码:

public interface Element {
    void accept(Visitor visitor);
    void accept(Visitor visitor, String param);
}

public class ConcreteElementA implements Element {
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    @Override
    public void accept(Visitor visitor, String param) {
        visitor.visit(this, param);
    }
}

public interface Visitor {
    void visit(ConcreteElementA elementA);
    void visit(ConcreteElementB elementB);
   void visit(ConcreteElementA elementA, String param);
}

public class ConcreteVisitorA implements Visitor {
    @Override
    public void visit(ConcreteElementA elementA) {
        System.out.println("ConcreteVisitorA visit ConcreteElementA");
    }

    @Override
    public void visit(ConcreteElementB elementB) {
        System.out.println("ConcreteVisitorA visit ConcreteElementB");
    }

    @Override
    public void visit(ConcreteElementA elementA, String param) {
        System.out.println("ConcreteVisitorA visit ConcreteElementA with param " + param);
    }
}

public class ConcreteVisitorB implements Visitor {
    @Override
    public void visit(ConcreteElementA elementA) {
        System.out.println("ConcreteVisitorB visit ConcreteElementA");
    }

    @Override
    public void visit(ConcreteElementB elementB) {
        System.out.println("ConcreteVisitorB visit ConcreteElementB");
    }

    @Override
    public void visit(ConcreteElementA elementA, String param) {
        System.out.println("ConcreteVisitorB visit ConcreteElementA with param " + param);
    }
}

public class ObjectStructure {
    private List<Element> elements = new ArrayList<>();

    public void attach(Element element) {
        elements.add(element);
    }

    public void detach(Element element) {
        elements.remove(element);
    }

    public void accept(Visitor visitor) {
        for (Element element : elements) {
            element.accept(visitor);
        }
    }

    public void accept(Visitor visitor, String param) {
        for (Element element : elements) {
            element.accept(visitor, param);
        }
    }
}

public class Client {
    public static void main(String[] args) {
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.attach(new ConcreteElementA());
        objectStructure.attach(new ConcreteElementB());

        Visitor visitorA = new ConcreteVisitorA();
        Visitor visitorB = new ConcreteVisitorB();

        objectStructure.accept(visitorA);
        objectStructure.accept(visitorB);
        objectStructure.accept(visitorA, "paramA");
        objectStructure.accept(visitorB, "paramB");
    }
}

在上述代码中,我们定义了一个新的 accept() 方法,该方法接收一个额外的参数 param,并在具体元素对象的 accept() 方法中将该参数传递给访问者对象的 visit() 方法。然后,我们创建了两个具体访问者对象 visitorA 和 visitorB,并分别调用 objectStructure 的 accept() 方法,将这两个访问者对象作为参数传入,并在第二个 accept() 方法中传递了参数 "paramA" 和 "paramB"。在访问者对象的 visit() 方法中,我们可以根据传递的参数进行不同的操作。

双重分派的优点是可以根据传递的参数实现不同的操作,从而增强了访问者模式的灵活性和扩展性。

然而,双重分派也有一些缺点。首先,它会导致类的层次结构变得复杂,因为每个具体元素对象都需要实现多个 accept() 方法。其次,双重分派会增加代码的复杂性,因为需要在访问者对象中定义多个具体的 visit() 方法,并在具体元素对象中重载多个 accept() 方法。最后,双重分派可能会导致代码的可读性变差,因为需要理解多个层次的调用关系。

因此,在使用双重分派时需要谨慎考虑,避免过度使用,以免导致代码的复杂性和可维护性下降。

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: java spring 教程 设计模式 访问者 详解
最后更新:2023年 5月 27日

墨风如雪

一个热爱生活,热爱分享的程序员

打赏 点赞
< 上一篇
下一篇 >

文章评论

您需要 登录 之后才可以评论

墨风如雪

一个热爱生活,热爱分享的程序员

最新 热点 随机
最新 热点 随机
阿里WebAgent开源:引领自主搜索新纪元 重磅炸弹!字节跳动开源BAGEL:70亿参数,统一多模态理解与生成,AI“全能王”诞生记! 小米MiMo-VL:7B参数,怎么就成了多模态界的“越级打怪王”? 炸裂!DeepSeek 8B 量化版降临:告别显存焦虑,你的 3080 Ti 也能玩转顶级大模型了! 美团炸场AI圈:点外卖点出个软件?用「对话式编程」重塑生产力! 当你的证件照学会了眨眼微笑:腾讯混元 HunyuanPortrait 开源,让数字肖像「活过来」!
重塑AI推理格局?微软Phi-4模型震撼发布:轻量化性能炸裂炸裂!微软这门免费AI Agent新手课,GitHub近2万星,简直是宝藏!ComfyUI“打通任督二脉”:直接调用Veo2、GPT-4o等65大模型!一键串联你的AI工作流AI圈炸锅了!Mistral Medium 3:性能 SOTA,成本打骨折,企业玩家的新宠?字节终于开源“扣子”同款引擎了!FlowGram:AI 时代的可视化工作流利器告别“微信黑箱”!Chatlog:让你的聊天记录也能拥有“AI大脑”!
java spring 当中后置处理器 Google AI Studio免费开放Gemini 2.0 Flash Experimental画图模型:一场创意设计的革命 不同的角度看待问题 ArangoDB你会使用吗? spring面试题 循环依赖 炸裂!OpenAI 不声不响发布 GPT-4.1 全家桶,开发者狂喜:更快、更强、还更便宜? DeepSeek R1 API替代方案全解析:手把手教你无缝迁移至硅基流动(附实战代码)
标签聚合
java 算法 动态规划 deepseek AI 教程 spring 设计模式

COPYRIGHT © 2023 墨风如雪博客. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

免责声明 - 隐私政策