【设计模式深度剖析】【9】【行为型】【访问者模式】| 以博物馆的导览员为例加深理解

发布于:2024-06-20 ⋅ 阅读:(55) ⋅ 点赞:(0)

👈️上一篇:备忘录模式    |   下一篇:状态模式👉️

设计模式-专栏👈️


访问者模式

访问者模式(Visitor Pattern)就像一位多才多艺的导游,能够根据不同的景点(数据结构中的元素)提供个性化的解说服务(操作),而无需改变景点的本质。

定义

英文原话

The Visitor pattern is a behavioral design pattern that allows you to add new operations to objects without changing their classes. A visitor is a class with methods that correspond to the classes of an object structure it visits. A client uses the visitor to perform the operations on the elements of the object structure.

直译

访问者模式是一种行为设计模式,它允许我们在不改变对象类的情况下为对象添加新的操作。访问者是一个类,它包含的方法对应于它所访问的对象结构中的类。客户端使用访问者来对对象结构的元素执行操作。

如何理解呢?

想象一下我们有一个装满各种形状(圆形、矩形、三角形)的盒子。现在我们想对这些形状进行不同的操作,比如测量它们的面积或周长。使用访问者模式,我们可以创建一个“测量工具”(即访问者),它知道如何与每种形状交互并获取所需的信息,而不需要改变形状本身。

访问者模式的角色

访问者模式中的角色包括:

  1. Visitor(访问者):这是一个接口或抽象类,声明了访问特定元素类时需要执行的操作。
  2. ConcreteVisitor(具体访问者):实现了Visitor接口或继承了Visitor抽象类,实现了对元素类中每个元素的访问操作。
  3. Element(元素):这是一个接口或抽象类,声明了一个接受访问者对象的方法(通常是accept)。
  4. ConcreteElement(具体元素):实现了Element接口或继承了Element抽象类,并实现accept方法以接受访问者的访问。
  5. ObjectStructure(对象结构):能够枚举它的元素,并可以提供一个高层接口以允许访问者访问它的元素。这通常是一个集合类,如列表或树。

类图

在这里插入图片描述

代码示例

package com.polaris.designpattern.list3.behavioral.pattern09.visitor.classicdemo;

import java.util.ArrayList;
import java.util.List;

// Visitor 接口
interface Visitor {
    void visit(ConcreteElementA element);

    void visit(ConcreteElementB element);
}

// ConcreteVisitor 类  
class ConcreteVisitor implements Visitor {
    @Override
    public void visit(ConcreteElementA element) {
        System.out.println("Visiting ConcreteElementA: " + element.operationA());
    }

    @Override
    public void visit(ConcreteElementB element) {
        System.out.println("Visiting ConcreteElementB: " + element.operationB());
    }
}

// Element 接口  
interface Element {
    void accept(Visitor visitor);
}

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

    public String operationA() {
        return "elementA info...";
    }
}

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

    public String operationB() {
        return "elementB info...";
    }
}

// ObjectStructure 类(这里简单用ArrayList作为示例)

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

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

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

// 客户端代码  
public class VisitorPatternDemo {
    public static void main(String[] args) {
        ObjectStructure objectStructure = new ObjectStructure();
        objectStructure.add(new ConcreteElementA());
        objectStructure.add(new ConcreteElementB());

        Visitor visitor = new ConcreteVisitor();
        objectStructure.accept(visitor);
    }
}
/* Output:
Visiting ConcreteElementA: elementA info...
Visiting ConcreteElementB: elementB info...
*///~

在这个示例中,我们有一个Visitor接口和两个具体的访问者方法(对应于两种不同类型的元素)。我们还有两个实现了Element接口的ConcreteElement类,它们各自有自己的操作。ObjectStructure类管理了一个Element对象的集合,并提供了一个accept方法来遍历这些对象并接受访问者的访问。在客户端代码中,我们创建了一个ObjectStructure对象,并向其中添加了两个不同类型的元素。然后,我们创建了一个ConcreteVisitor对象,并使用它来访问ObjectStructure中的所有元素。

访问者模式的应用

访问者模式是一种设计模式,它允许在不改变数据结构的前提下,为数据结构中的每个元素定义新的操作。这种模式在需要为数据结构中的元素添加新行为,但又不想修改这些元素所在类的情况下特别有用。

优点

  1. 扩展性好:当需要为数据结构中的元素添加新操作时,只需定义一个新的访问者类即可,无需修改原有类。
  2. 复用性好:访问者模式可以通过为不同的数据结构定义相同的访问者接口,实现操作的复用。
  3. 灵活性好:访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可以相对自由地演化而不影响系统的数据结构。
  4. 符合单一职责原则:每个访问者只负责一种操作,使代码更加清晰和易于维护。

缺点

  1. 破坏封装:在访问者模式中,具体元素需要向访问者公开其内部状态和方法,这可能会破坏对象的封装性。
  2. 增加新数据结构困难当需要为新的数据结构添加访问者时,需要在访问者接口中添加新的方法,这可能会增加代码的复杂性。
  3. 具体元素变更困难如果具体元素的内部结构发生变化,可能需要修改所有相关的访问者类,这可能会增加维护成本。

使用场景

  1. 电商网站商品分类与操作:电商网站通常有大量的商品,这些商品可以按照不同的属性进行分类。使用访问者模式,可以定义一个访问者对象,它能够访问不同类型的商品对象,并根据需要执行不同的操作,如按照价格排序、根据品牌筛选等。
  2. 图形编辑器的操作处理:在图形编辑器中,有多种图形元素,如线条、圆形、矩形等。访问者模式允许定义一个访问者对象,它能够访问这些图形元素并执行不同的操作,如移动、缩放、旋转或改变颜色等。
  3. 编译器和解释器设计:在编译器或解释器的设计中,抽象语法树(AST)是一个常见的数据结构。使用访问者模式,可以为AST中的不同节点类型定义不同的访问者,以执行如类型检查、代码优化、代码生成等操作。

将源程序表示为一个抽象语法树,编译器需要在抽象语法树上实施某些操作以进行“静态语义分析”,例如检查是否所有的变量都已经被定义了。他也需要生成代码。因此他可能要定义许多操作以进行类型检查、代码优化、流程分析,检查变量是否在使用前被赋初值,等等。此外,还可以使用抽象语法树进行优美格式打印、程序重构、code instrumentation(代码插装,见下面note注释内容)以及对程序进行多种度量。


这些操作大多要求对不同的节点进行不同的处理。例如对代表赋值语句的结点的处理就不同于对代表变量或算术表达式的结点的处理。


因此,有用于赋值语句的类,有用于变量访问的类,还有用于算术表达式的类等等。结点类的集合当然依赖于被编译的语言,但对于一个给定的语言其变化不大(即已有的数据结构变化不大或不变)。

note: Code instrumentation(代码仪器化)是一种软件开发领域的技术。详细解释如下:

  1. 定义
    • Code instrumentation涉及向代码中插入特定的指令或代码片段,以便在程序执行过程中收集各种信息或执行特定的任务。
  2. 用途
    • 调试:通过插入额外的代码,可以更容易地跟踪和调试程序的行为。
    • 性能分析:插入的代码可以收集程序的性能数据,如执行时间、内存使用等。
    • >代码覆盖率分析>:用于确定哪些代码在测试过程中被实际执行过。
    • 安全检查:可以插入用于检测潜在安全问题的代码。
  3. 实现
    • 通过在代码中嵌入仪器化代码,开发人员可以更深入地了解程序的执行过程,并获取关键的运行时信息。
  4. 其他表述
    • Code instrumentation有时也被称为**“代码插装”“代码插桩”**。
  5. 应用实例
    • 在自动化测试和内部质量保证(QA)工具中,code instrumentation被用于基础设施、实施和维护,以及支持代码覆盖率的分析

总结来说,code instrumentation是一种通过向代码中插入特定指令或代码片段来收集信息或执行任务的软件开发技术,它在调试、性能分析、代码覆盖率分析和安全检查等方面发挥着重要作用。

示例解析:博物馆的导览员

假设你是一家博物馆的导览员,博物馆里有很多不同类型的展品,如绘画、雕塑和古代文物。作为导览员,你需要为不同类型的展品提供不同的解说词。但是,展品的种类可能会随着时间增加或减少,而你不希望每次有新的展品加入时都要重新学习所有的解说词。

在这里,展品可以看作是数据结构中的元素,而导览员就是访问者。导览员(访问者)需要访问不同的展品(元素),并为它们提供解说词(操作)。

代码示例

下面是一个简单的Java代码示例,用于模拟这个场景:

package com.polaris.designpattern.list3.behavioral.pattern09.visitor.demo1;

// 展品接口
interface Artifact {
    void accept(TourGuide tourGuide);
}

// 绘画展品  
class Painting implements Artifact {
    private String name;

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

    @Override
    public void accept(TourGuide tourGuide) {
        tourGuide.visit(this);
    }

    public String getName() {
        return name;
    }
}

// 雕塑展品  
class Sculpture implements Artifact {
    private String name;

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

    @Override
    public void accept(TourGuide tourGuide) {
        tourGuide.visit(this);
    }

    public String getName() {
        return name;
    }
}

// 导览员(访问者)接口  
interface TourGuide {
    void visit(Painting painting);

    void visit(Sculpture sculpture);
    // 如果以后有更多类型的展品,可以在这里添加方法  
}

// 具体导览员类  
class EnglishTourGuide implements TourGuide {
    @Override
    public void visit(Painting painting) {
        System.out.println("This is a painting called " + painting.getName() + ". It is beautiful!");
    }

    @Override
    public void visit(Sculpture sculpture) {
        System.out.println("This is a sculpture called " + sculpture.getName() + ". It is impressive!");
    }
}

// 客户端代码  
public class MuseumDemo {
    public static void main(String[] args) {
        // 创建展品  
        Artifact painting = new Painting("Mona Lisa");
        Artifact sculpture = new Sculpture("Thinker");

        // 创建导览员  
        TourGuide tourGuide = new EnglishTourGuide();

        // 导览员访问展品  
        painting.accept(tourGuide);
        sculpture.accept(tourGuide);
    }
}

/* Output:
This is a painting called Mona Lisa. It is beautiful!
This is a sculpture called Thinker. It is impressive!
*///~

在这个示例中,Artifact是展品接口,PaintingSculpture是具体的展品类。TourGuide是导览员接口,EnglishTourGuide是具体的导览员类。


当新的展品类型出现时,只需要实现Artifact接口并添加相应的accept方法,然后在TourGuide接口中添加对应的visit方法即可,而不需要修改已经存在的展品类和导览员类(除了添加新的方法外)。这样,就实现了在不改变数据结构的前提下,为数据结构中的每个元素定义新的操作。

note1: 当系统需要添加新的操作到已有的数据结构(如不同的展品类型)时,我们可以通过增加一个新的访问者类来实现,而无需修改原有的数据结构。

note2:当新的展品类型出现时,需要:

  1. 实现Artifact接口来定义新的展品类(比如Ceramic代表陶瓷展品)。
  2. Artifact接口的实现类(如Ceramic)中,实现accept方法以允许导览员(TourGuide)访问。
  3. 修改TourGuide接口,添加一个新的visit方法来处理新的展品类型(比如void visit(Ceramic ceramic);)。
  4. TourGuide的实现类(如EnglishTourGuide)中,实现新添加的visit方法以提供针对新展品类型的解说词。

这样,当新的展品类型被添加到博物馆时,导览员(TourGuide)就能够处理它,而不需要修改已经存在的展品类和导览员类(除了添加新的方法外)。这种设计使得系统的扩展性很好,能够轻松应对新的展品类型的添加。


👈️上一篇:备忘录模式    |   下一篇:状态模式👉️

设计模式-专栏👈️


网站公告

今日签到

点亮在社区的每一天
去签到