【再谈设计模式】访问者模式~操作对象结构的新视角

简介:   访问者模式是一种行为设计模式,旨在解决对象结构与操作逻辑的耦合问题。在软件系统开发中,当面临复杂的对象结构(如多种类型对象组成的树形或图形结构),且需要对这些对象执行不同操作时,传统方式将操作直接写在对象类中会导致类职责过多,不利于维护和扩展。而访问者模式通过将操作与对象结构分离,允许在不改变现有对象结构的情况下定义新操作,元素接受访问者访问,访问者定义对不同类型元素的操作逻辑,从而为应对这种复杂情况提供了有效的解决方案。

{BFCDE3C5-04CE-4698-BC8A-0817B4AD8F70}.png

引言

访问者模式是一种行为设计模式,旨在解决对象结构与操作逻辑的耦合问题。在软件系统开发中,当面临复杂的对象结构(如多种类型对象组成的树形或图形结构),且需要对这些对象执行不同操作时,传统方式将操作直接写在对象类中会导致类职责过多,不利于维护和扩展。而访问者模式通过将操作与对象结构分离,允许在不改变现有对象结构的情况下定义新操作,元素接受访问者访问,访问者定义对不同类型元素的操作逻辑,从而为应对这种复杂情况提供了有效的解决方案。

一、定义与描述

访问者模式是一种行为设计模式,它允许你在不修改现有对象结构的情况下定义新的操作。这种模式将算法与对象结构分离,使得在增加新的操作时不需要对对象结构中的元素类进行修改。

b2f493df61c09712a0c928ffe7a8c730_fd33cea7c1012e20d89be80a6bf72192.png

二、抽象背景

在许多软件系统中,我们经常会遇到对一组对象进行操作的情况。这些操作可能会随着系统的发展而不断增加。如果直接在对象类中添加这些操作,会导致类的职责过重,违背单一职责原则,并且对象类的结构可能会变得复杂且难以维护。访问者模式的出现就是为了解决这个问题,它将这些操作封装到独立的访问者类中。
{92F339E4-4E8F-4AD5-9017-592256460098}.png

三、适用场景与现实问题解决

数据结构与操作分离

当我们有一个复杂的数据结构(如树形结构、图形结构),并且需要对这个结构中的元素执行多种不同的操作时,访问者模式非常有用。例如,在编译器中,对抽象语法树(AST)的操作,如类型检查、代码生成等,可以使用访问者模式将这些操作与AST的节点类分离。
{04E0E837-1495-428C-A518-FA953F99581F}.png

操作易变场景

如果操作的逻辑经常变化,使用访问者模式可以方便地添加、修改或删除操作,而不会影响到对象结构。比如在一个电商系统中,对于商品的价格计算可能有多种方式(普通用户价格、会员价格、促销价格等),这些价格计算逻辑可以通过访问者模式与商品类分离。

四、访问者模式的现实生活的例子

eee919f173f78da487ecf463c6975a9d_6681f9c5c1ad4e7dafdfbe9c9f4f5d76.png

医院看病

医院有不同类型的科室(如内科、外科、牙科等),这些科室可以看作是被访问的对象结构中的元素。而医生可以看作是访问者。不同的医生(不同的访问者)对不同科室(不同的元素)有不同的操作(诊断、治疗等)。例如,内科医生会对内科病人进行特定的检查和治疗,外科医生则对需要手术的病人进行操作,而不会影响科室的基本结构。
{42CE9E30-0ADE-45FE-AE20-A8F57FBE9424}.png

旅游景点

旅游景点有各种不同的景点(如古建筑、自然景观、游乐设施等),游客是访问者。不同的游客(如摄影师、历史学家、普通游客)对不同的景点有不同的行为(摄影师会拍照,历史学家会研究古建筑的历史,普通游客则是观光)。景点的结构(布局、设施等)不会因为游客的不同行为而改变。

五、初衷与问题解决

初衷是为了在不改变对象结构的情况下,方便地增加新的操作。通过将操作封装到访问者类中,解决了对象类职责过重、操作与对象结构耦合过强的问题。当需要添加新的操作时,只需要创建一个新的访问者类,而不需要修改对象结构中的元素类。

六、代码示例

Java示例
{EE90AC65-8885-4DF6-BD3A-2DE0BED103D2}.png

{20EA7038-6E38-45B4-B867-641AA834379F}.png

{D735492E-A3CF-4F8B-B5E5-80D008DCF204}.png

// 元素接口interface Element {    void accept(Visitor visitor);
}// 具体元素Aclass ConcreteElementA implements Element {    @Override
    public void accept(Visitor visitor) {
   
        visitor.visit(this);
    }    public String operationA() {
           return "ConcreteElementA operation";
    }
}// 具体元素Bclass ConcreteElementB implements Element {    @Override
    public void accept(Visitor visitor) {
   
        visitor.visit(this);
    }    public String operationB() {
           return "ConcreteElementB operation";
    }
}// 访问者接口interface Visitor {    void visit(ConcreteElementA element);    void visit(ConcreteElementB element);
}// 具体访问者class ConcreteVisitor implements Visitor {    @Override
    public void visit(ConcreteElementA element) {
   
        System.out.println("ConcreteVisitor visits " + element.operationA());
    }    @Override
    public void visit(ConcreteElementB element) {
   
        System.out.println("ConcreteVisitor visits " + element.operationB());
    }
}public class VisitorPatternJava {
       public static void main(String[] args) {
           Element elementA = new ConcreteElementA();        Element elementB = new ConcreteElementB();        Visitor visitor = new ConcreteVisitor();

        elementA.accept(visitor);
        elementB.accept(visitor);
    }
}

C++示例


#include <iostream>// 抽象元素类class Element {public:    virtual void accept(class Visitor* visitor) = 0;
};// 具体元素Aclass ConcreteElementA : public Element {public:    void accept(Visitor* visitor) override;    std::string operationA() {        return "ConcreteElementA operation";
    }
};// 具体元素Bclass ConcreteElementB : public Element {public:    void accept(Visitor* visitor) override;    std::string operationB() {        return "ConcreteElementB operation";
    }
};// 抽象访问者类class Visitor {public:    virtual void visit(ConcreteElementA* element) = 0;    virtual void visit(ConcreteElementB* element) = 0;
};// 具体访问者类class ConcreteVisitor : public Visitor {public:    void visit(ConcreteElementA* element) override {
   
        std::cout << "ConcreteVisitor visits " << element->operationA() << std::endl;
    }    void visit(ConcreteElementB* element) override {
   
        std::cout << "ConcreteVisitor visits " << element->operationB() << std::endl;
    }
};void ConcreteElementA::accept(Visitor* visitor) {
   
    visitor->visit(this);
}void ConcreteElementB::accept(Visitor* visitor) {
   
    visitor->visit(this);
}int main() {
   
    Element* elementA = new ConcreteElementA();
    Element* elementB = new ConcreteElementB();
    Visitor* visitor = new ConcreteVisitor();

    elementA->accept(visitor);
    elementB->accept(visitor);    delete elementA;    delete elementB;    delete visitor;    return 0;
}

Python示例


# 抽象元素类class Element:    def accept(self, visitor):        pass# 具体元素Aclass ConcreteElementA(Element):    def accept(self, visitor):
        visitor.visit(self)    def operationA(self):        return "ConcreteElementA operation"# 具体元素Bclass ConcreteElementB(Element):    def accept(self, visitor):
        visitor.visit(self)    def operationB(self):        return "ConcreteElementB operation"# 抽象访问者类class Visitor:    def visit(self, element):        pass# 具体访问者类class ConcreteVisitor(Visitor):    def visit(self, element):        if isinstance(element, ConcreteElementA):            print(f"ConcreteVisitor visits {element.operationA()}")        elif isinstance(element, ConcreteElementB):            print(f"ConcreteVisitor visits {element.operationB()}")if __name__ == "__main__":
    elementA = ConcreteElementA()
    elementB = ConcreteElementB()
    visitor = ConcreteVisitor()

    elementA.accept(visitor)
    elementB.accept(visitor)
Go示例

package mainimport (    "fmt")// 元素接口type Element interface {
   
    accept(Visitor)
}// 具体元素Atype ConcreteElementA struct{}func (c *ConcreteElementA) accept(v Visitor) {
   
    v.visit(c)
}func (c *ConcreteElementA) operationA() string {
       return "ConcreteElementA operation"}// 具体元素Btype ConcreteElementB struct{}func (c *ConcreteElementB) accept(v Visitor) {
   
    v.visit(c)
}func (c *ConcreteElementB) operationB() string {
       return "ConcreteElementB operation"}// 访问者接口type Visitor interface {
   
    visit(*ConcreteElementA)
    visit(*ConcreteElementB)
}// 具体访问者type ConcreteVisitor struct{}func (c *ConcreteVisitor) visit(elementA *ConcreteElementA) {
   
    fmt.Printf("ConcreteVisitor visits %s\n", elementA.operationA())
}func (c *ConcreteVisitor) visit(elementB *ConcreteElementB) {
   
    fmt.Printf("ConcreteVisitor visits %s\n", elementB.operationB())
}func main() {
   
    elementA := &ConcreteElementA{
   }
    elementB := &ConcreteElementB{
   }
    visitor := &ConcreteVisitor{
   }

    elementA.accept(visitor)
    elementB.accept(visitor)
}

七、访问者模式的优缺点

优点

解耦操作与对象结构

    使得操作和对象结构可以独立变化,提高了代码的可维护性和可扩展性。

易于添加新操作

    当有新的操作需求时,只需创建新的访问者类,不需要修改对象结构中的元素类。

符合单一职责原则

    元素类专注于自身的结构和数据,访问者类专注于操作逻辑。

缺点

违反开闭原则

    如果要在对象结构中增加新的元素类,可能需要修改所有的访问者类,这违反了开闭原则。

增加复杂性

    访问者模式引入了额外的层次结构(访问者和元素的多态调用),增加了代码的复杂性,对于简单的应用场景可能会显得过于复杂。

八、访问者模式的升级版

双分派访问者模式

    传统的访问者模式是单分派的,即根据对象的运行时类型选择要执行的方法。双分派访问者模式在此基础上,通过在访问者和元素之间进行两次方法调用,根据访问者和元素的具体类型来确定操作。这种方式可以更灵活地处理不同类型的元素和访问者之间的交互,进一步提高了模式的灵活性。

反射式访问者模式

    利用编程语言的反射机制,可以动态地创建访问者类或者确定访问者类中的操作方法。这种模式在处理复杂的对象结构和动态变化的操作需求时非常有用。例如,在一个插件式的系统中,新的插件(可以看作是访问者)可能会不断加入,反射式访问者模式可以在不重新编译整个系统的情况下,根据插件的信息动态地执行相应的操作。

基于接口的访问者模式改进

    在传统的访问者模式中,访问者接口和元素接口可能会因为元素类型和操作的增加而变得臃肿。可以采用基于接口的改进方式,将访问者接口和元素接口拆分成更小的子接口,每个子接口负责特定类型的操作或元素。这样可以提高代码的可读性和可维护性,并且更符合接口隔离原则。
目录
相关文章
|
1月前
|
设计模式 存储 Java
【再谈设计模式】备忘录模式~对象状态的守护者
备忘录模式属于行为型设计模式。它的主要目的是在不破坏对象封装性的前提下,捕获并外部化一个对象的内部状态,以便之后可以将该对象恢复到这个状态。原发器(Originator):创建一个备忘录,用于记录当前时刻它的内部状态。原发器还可以使用备忘录来恢复其内部状态。备忘录(Memento):存储原发器对象的内部状态。备忘录应该防止原发器以外的其他对象访问其内部状态。负责人(Caretaker):负责保存备忘录,但不能对备忘录的内容进行操作或检查。
208 82
|
2月前
|
设计模式 供应链 安全
【再谈设计模式】中介者模式 - 协调对象间交互的枢纽
中介者模式定义了一个中介对象来封装一组对象之间的交互方式。中介者使得各对象之间不需要显式地相互引用,从而降低了它们之间的耦合度。它通过将对象之间的交互逻辑集中到中介者对象中,使得系统的结构更加清晰,易于维护和扩展。
68 18
【再谈设计模式】中介者模式 - 协调对象间交互的枢纽
|
2月前
|
设计模式 Java Go
【再谈设计模式】状态模式~对象行为的状态驱动者
状态模式属于行为型设计模式。它将对象的行为封装在不同的状态类中,使得对象在不同的状态下表现出不同的行为。上下文(Context):这是一个包含状态对象的类,它定义了客户感兴趣的接口,并维护一个具体状态对象的引用。上下文将操作委托给当前的状态对象来处理。抽象状态(State):这是一个抽象类或者接口,它定义了一个特定状态下的行为接口。所有具体的状态类都实现这个接口。具体状态(Concrete State):这些是实现抽象状态接口的类,每个具体状态类实现了与该状态相关的行为。
84 18
|
4月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
4月前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
4月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
6月前
|
设计模式 JavaScript 前端开发
JavaScript设计模式--访问者模式
【10月更文挑战第1天】
63 3
|
7月前
|
设计模式 算法 数据库连接
PHP中的设计模式:如何优化你的代码结构
在本文中,我们将深入探讨PHP中的设计模式。设计模式是解决常见软件设计问题的最佳实践。它们不是具体的代码,而是一种编程经验的总结。掌握设计模式可以帮助你写出更高效、灵活和可维护的代码。本文将介绍几种常见的设计模式,并通过示例展示如何在PHP项目中应用这些模式。无论你是PHP初学者还是有经验的开发者,都能从本文中获得启发和实用的技巧。
|
7月前
|
设计模式 缓存 算法
Java设计模式-访问者模式(22)
Java设计模式-访问者模式(22)
109 0
|
4月前
|
设计模式 前端开发 搜索推荐
前端必须掌握的设计模式——模板模式
模板模式(Template Pattern)是一种行为型设计模式,父类定义固定流程和步骤顺序,子类通过继承并重写特定方法实现具体步骤。适用于具有固定结构或流程的场景,如组装汽车、包装礼物等。举例来说,公司年会节目征集时,蜘蛛侠定义了歌曲的四个步骤:前奏、主歌、副歌、结尾。金刚狼和绿巨人根据此模板设计各自的表演内容。通过抽象类定义通用逻辑,子类实现个性化行为,从而减少重复代码。模板模式还支持钩子方法,允许跳过某些步骤,增加灵活性。
245 11

热门文章

最新文章

下一篇
oss创建bucket