Qt C++设计模式->访问者模式

news2024/10/12 12:34:44

访问者模式(Visitor Pattern)是一种行为型设计模式,它将操作与对象结构分离,使得你可以在不改变对象结构的前提下定义作用于这些对象的新操作。访问者模式通过引入一个访问者对象,允许你在不修改类的前提下向已有类添加新的行为。

访问者模式的核心思想

访问者模式的主要思想是将对象结构的操作封装到访问者中。对象结构中的每个元素都可以接受一个访问者,并让这个访问者对自己进行操作。这使得我们可以很方便地增加新的操作,而不需要修改对象的类。

访问者模式的主要组成部分包括:

  1. 访问者接口(Visitor):为每个对象结构中的元素定义访问操作。每个操作方法对应一个元素类型。

  2. 具体访问者(Concrete Visitor):实现访问者接口,定义每个元素对应的具体操作。

  3. 元素接口(Element):定义接受访问者的方法。这个方法通常会将自身作为参数传递给访问者。

  4. 具体元素(Concrete Element):实现元素接口,并在接受访问者时调用访问者的相应操作。

  5. 对象结构(Object Structure):通常是一个包含多个元素的集合,负责遍历这些元素,并让它们接受访问者。

访问者模式的应用场景

  1. 需要对类的对象结构进行复杂操作:例如编译器中的语法树、绘图系统中的图形对象层次等。访问者模式允许你将复杂的操作分离出来,而不影响对象结构。

  2. 不方便修改对象结构:当对象结构比较稳定,频繁添加新行为时,访问者模式可以让你在不修改这些对象类的前提下,增加新的操作。

  3. 操作与结构的分离:当你希望将操作与对象结构进行解耦时,访问者模式非常适用。

访问者模式的示例代码

假设我们有一个对象结构,包含不同类型的形状,如圆形、矩形、三角形等。我们希望对这些形状进行不同的操作,例如计算面积、绘制形状等。访问者模式可以帮助我们在不修改形状类的前提下添加这些操作。

1. 定义访问者接口、元素接口和具体实现类

#include <QDebug>
#include <QString>
#include <QList>

// 前向声明
class Circle;
class Rectangle;
class Triangle;

// 访问者接口:定义访问每个元素的操作
class Visitor {
public:
    virtual void visitCircle(Circle* circle) = 0;
    virtual void visitRectangle(Rectangle* rectangle) = 0;
    virtual void visitTriangle(Triangle* triangle) = 0;
    virtual ~Visitor() = default;
};

// 元素接口:定义接受访问者的方法
class Shape {
public:
    virtual void accept(Visitor* visitor) = 0;
    virtual ~Shape() = default;
};

// 具体元素类:圆形
class Circle : public Shape {
private:
    int radius;

public:
    Circle(int r) : radius(r) {}

    int getRadius() const {
        return radius;
    }

    void accept(Visitor* visitor) override {
        visitor->visitCircle(this);  // 让访问者访问自己
    }
};

// 具体元素类:矩形
class Rectangle : public Shape {
private:
    int width, height;

public:
    Rectangle(int w, int h) : width(w), height(h) {}

    int getWidth() const {
        return width;
    }

    int getHeight() const {
        return height;
    }

    void accept(Visitor* visitor) override {
        visitor->visitRectangle(this);  // 让访问者访问自己
    }
};

// 具体元素类:三角形
class Triangle : public Shape {
private:
    int base, height;

public:
    Triangle(int b, int h) : base(b), height(h) {}

    int getBase() const {
        return base;
    }

    int getHeight() const {
        return height;
    }

    void accept(Visitor* visitor) override {
        visitor->visitTriangle(this);  // 让访问者访问自己
    }
};

// 具体访问者类:计算形状面积
class AreaVisitor : public Visitor {
public:
    void visitCircle(Circle* circle) override {
        int radius = circle->getRadius();
        double area = 3.14 * radius * radius;
        qDebug() << "Circle area:" << area;
    }

    void visitRectangle(Rectangle* rectangle) override {
        int area = rectangle->getWidth() * rectangle->getHeight();
        qDebug() << "Rectangle area:" << area;
    }

    void visitTriangle(Triangle* triangle) override {
        double area = 0.5 * triangle->getBase() * triangle->getHeight();
        qDebug() << "Triangle area:" << area;
    }
};

// 具体访问者类:绘制形状
class DrawVisitor : public Visitor {
public:
    void visitCircle(Circle* circle) override {
        qDebug() << "Drawing a circle with radius" << circle->getRadius();
    }

    void visitRectangle(Rectangle* rectangle) override {
        qDebug() << "Drawing a rectangle with width" << rectangle->getWidth() << "and height" << rectangle->getHeight();
    }

    void visitTriangle(Triangle* triangle) override {
        qDebug() << "Drawing a triangle with base" << triangle->getBase() << "and height" << triangle->getHeight();
    }
};

// 对象结构:形状集合
class ShapeCollection {
private:
    QList<Shape*> shapes;

public:
    void addShape(Shape* shape) {
        shapes.append(shape);
    }

    void accept(Visitor* visitor) {
        for (Shape* shape : shapes) {
            shape->accept(visitor);  // 让每个形状接受访问者
        }
    }

    ~ShapeCollection() {
        qDeleteAll(shapes);
    }
};

// 使用示例
int main() {
    // 创建一些形状
    Shape* circle = new Circle(5);
    Shape* rectangle = new Rectangle(4, 6);
    Shape* triangle = new Triangle(3, 4);

    // 创建形状集合
    ShapeCollection shapeCollection;
    shapeCollection.addShape(circle);
    shapeCollection.addShape(rectangle);
    shapeCollection.addShape(triangle);

    // 创建访问者:计算面积
    AreaVisitor* areaVisitor = new AreaVisitor();
    qDebug() << "Calculating areas:";
    shapeCollection.accept(areaVisitor);

    // 创建访问者:绘制形状
    DrawVisitor* drawVisitor = new DrawVisitor();
    qDebug() << "\nDrawing shapes:";
    shapeCollection.accept(drawVisitor);

    // 清理内存
    delete areaVisitor;
    delete drawVisitor;

    return 0;
}

代码解析

  • Visitor接口:定义了访问不同类型形状的操作(visitCirclevisitRectanglevisitTriangle)。

  • Shape接口:定义了接受访问者的方法(accept),每个具体的形状都会调用访问者的相应方法,并将自身传递给访问者。

  • 具体元素类(Circle、Rectangle、Triangle):这些类实现了Shape接口,并在accept方法中调用访问者的具体方法。

  • AreaVisitor和DrawVisitor类:这两个具体的访问者实现了访问者接口,用于计算形状的面积和绘制形状。每个访问者都提供了不同的功能,而不需要修改Shape类。

  • ShapeCollection类:这是一个对象结构,包含多个Shape对象,并让访问者依次访问每个形状。

访问者模式的优点

  • 增加操作非常容易:当需要对对象结构中的元素添加新的操作时,只需要增加新的访问者,而不需要修改已有的类,这符合开闭原则。

  • 分离关注点:访问者模式将不同操作(如计算面积、绘制形状等)分离到不同的访问者中,从而避免了将操作逻辑分散到元素类中,提高了系统的可维护性。

  • 符合单一职责原则:每个访问者只专注于一种操作,不会影响对象结构的定义和职责。

访问者模式的缺点

  • 违反了依赖倒置原则:访问者模式要求元素类依赖访问者接口,这在某种程度上违反了依赖倒置原则。因为对象结构中的元素需要接受访问者,这使得元素类必须知道访问者的存在。

  • 增加了复杂性:当对象结构中的元素种类较多时,访问者接口和访问者类会变得非常庞大,尤其是当每个访问者都需要实现多个访问方法时,复杂度会增加。

  • 元素类的变化困难:如果对象结构中的元素类需要频繁变化(如添加新的字段或属性),那么每个访问者类都需要修改,增加了维护成本。

适合使用访问者模式的情况

  • 需要对类结构中的对象添加新行为:如果频繁需要对类结构中的对象添加新的操作,访问者模式是一个很好的选择,因为你可以通过创建新的访问者来实现新行为,而不需要修改原有的类。

  • 稳定的类结构:访问者模式适合对象结构较为稳定、不频繁修改的情况,因为它通过增加访问者来扩展操作,而不是通过修改对象结构本身。

不适合使用访问者模式的情况

  • 对象结构频繁变化:如果对象结构中的类经常发生变化(例如添加新类型的元素或修改现有元素),访问者模式就不太适合,因为每次对象结构变化都需要修改所有访问者。

  • 简单系统:对于系统较为简单的情况,访问者模式会增加不必要的复杂性。

Qt中的访问者模式应用

在Qt开发中,访问者模式可以应用于复杂的UI组件、绘图系统或处理层次化对象的场景。例如,Qt的绘图系统中,可以有不同的图形元素(如圆形、矩形等),并且可能需要对这些图形元素进行不同的操作(如渲染、计算面积、检测碰撞等)。访问者模式可以帮助你将这些操作分离到不同的访问者中,从而提高系统的灵活性。

总结

访问者模式通过将操作与对象结构分离,使得你可以在不修改类结构的前提下增加新的操作。它非常适合那些对象结构相对稳定,但需要频繁添加操作的场景。尽管访问者模式在扩展新操作时非常灵活,但当对象结构变化较频繁时,可能会增加维护的复杂性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2207915.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

双语大脑的神经可塑性能力:来自健康和病理个体的见解

摘要 双语经验的神经印记对于理解大脑如何处理优势语言和非优势语言至关重要&#xff0c;但关于它的研究仍然没有定论。不同的研究表明神经处理存在相似性或差异性&#xff0c;这对患有脑肿瘤的双语患者具有重要意义。保留术后的双语功能需要考虑到术前的神经可塑性变化。在这…

【原创】可用于 Android Studio 的翻译插件

在不少讲解Android 开发的老师视频中会出现一个运行在Android Studio 上的翻译插件&#xff0c;感觉挺实用的。 接下来&#xff0c;我们把它安装在我们的Android Studio 上。 设置 点击右上角齿轮按钮&#xff0c;选择Settings 安装 翻译插件 输入Tanslation&#xff0c;选…

jmeter出参保存到文件,保存失败解决

1、添加JSON提取 2、添加beanshell FileWriter writer new FileWriter("C:/Users/xxx/Desktop/signUrl.csv", true); writer.write(vars.get("company_name")"\t"vars.get("signUrl")"\n"); writer.close(); 写文件的两个…

金融大数据平台总体技术

目录 金融大数据平台应用场景风险管理 场景描述解决方案​​​​​​​市场营销 ​​​​​​​场景描述解决方案​​​​​​​金融大数据信息价值链​​​​​​​金融大数据平台总体目标金融大数据平台功能技术要求​​​​​​​ ​​​​​​​概述数据接入功能要求 ​​…

鹦鸣app——服务端项目搭建

文章目录 服务端项目搭建创建启动程序构建全局初始化工厂函数创建app应用对象基于shell脚本启动项目加载项目配置数据库初始化SQLAlchemy初始化Redis数据库初始化mongoDB数据库初始化 日志初始化日志的等级flask日志功能的基本使用构建日志模块 蓝图初始化自动注册蓝图注册蓝图…

【C++】第五节:内存管理

1、C/C内存分布 看下面一段代码 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] { 1, 2, 3, 4 };char char2[] "abcd";const char* pChar3 "abcd";int* ptr1 (int*)malloc(s…

Axure PR 9 开关切换 设计交互

大家好&#xff0c;我是大明同学。 这期内容&#xff0c;我们来探讨Axure开关按钮设计与交互技巧​。 创建切换开关所需的元件 1.打开一个新的 RP 文件并在画布上打开 Page 1。 2.将“圆形”元件拖到画布上&#xff0c;在样式窗格中将高度和宽度设置为35&#xff0c;线段宽度…

[贪心+搜索] 马走日升级版

题目描述 国际象棋和中国象棋中&#xff0c;马的移动规则相同&#xff0c;都是走“日”字&#xff0c;我们将这种移动方式称为马步移动。如右图所示&#xff0c;从标号为 0 0 0 的点出发&#xff0c;可以经过一步马步移动达到标号为 1 1 1 的点&#xff0c;经过两步马步移动…

Excel中的常识

工作簿Workbook和工作表Worksheet什么关系&#xff1f; 工作簿&#xff1a;Workbook&#xff0c;也就是一个excel文件 工作表&#xff1a;Worksheet&#xff0c;也就是一个工作表&#xff0c;及Sheet文件 一个工作簿可以包含多个工作表。

IDM(Internet Download Manager)下载器的安装激活与换机方法

很多人都知道 Internet Download Manager(以下简称 IDM)是一款非常优秀的下载提速软件。它功能强大&#xff0c;几乎能下载网页中的所有数据&#xff08;包括视频、音频、图片等&#xff09;&#xff0c;且适用于现在市面上几乎所有的浏览器&#xff0c;非常受大家欢迎。IDM 是…

vue-插槽作用域实用场景

vue-插槽作用域实用场景 1.插槽1.1 自定义列表渲染1.2 数据表格组件1.3 树形组件1.4 表单验证组件1.5 无限滚动组件 1.插槽 插槽感觉知道有这个东西&#xff0c;但是挺少用过的&#xff0c;每次看到基本都会再去看一遍用法和概念。但是在项目里&#xff0c;自己还是没有用到过…

QD1-P1 开始学习前端,HTML、CSS与JS三者之间的关系

今天开始学习前端基础&#xff0c;新建专题《前端学习笔记1》保存前端基础学习笔记。 专题文章命名以qd1开头。 源课程 视频教程&#xff1a;【Web前端-最通俗易懂HTML、CSS与JS合集 1天速成】 up&#xff1a;遥遥温柔乡 在B站随便搜索了一个前端课程&#xff0c;共91节&am…

JVS低代码轻应用是什么?是如何拼装的?这篇文章讲的非常详细

1.1JVS轻应用是什么&#xff1f; 轻应用与传统应用的开发过程区别 传统开发&#xff08;原生开发&#xff09;采用的方式&#xff1a;①需求了解 ②产品原型③UI设计④建库建表⑤前端还原⑥后端开发⑦前后端联调⑧功能测试⑨部署上线轻应用开发方式&#xff08;配置化拼装&…

SpringBoot定时任务@Scheduled完整功能详解(提供Gitee源码)

目录 一、实现定时任务 1.1、fixedRate 1.2、fixedDelay 1.3、initialDelay 1.4、cron 二、cron表达式 三、读取配置文件 四、实现并行执行定时任务 五、Gitee源码 一、实现定时任务 首先在主应用类或者任何配置类上添加@EnableScheduling注解,以启用定时任务功能。…

基于monaco-editor的web日志组件

基于monaco-editor封装的编辑器&#xff0c;支持如下功能&#xff1a; 日志内容颜色配置&#xff1a;info、primary、success、warning、error支持主题配置&#xff1a;dark、light支持滚动到顶部、底部、全屏编辑器默认带的全局搜索扩展性强&#xff0c;支持monaco的所有配置…

STM32学习--4-1 OLED显示屏

接线图 OLED.c #include "stm32f10x.h" #include "OLED_Font.h"/*引脚配置*/ #define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x)) #define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))/*引脚初始化*/ void …

selenium:WebElement类的核心操作方法(3)

当我们通过webdriver中的find_element函数定位到元素后&#xff0c;其实返回的是WebElement对象&#xff0c;而该对象有很多重要的方法&#xff0c;比如输入文本&#xff0c;点击按钮&#xff0c;获取属性&#xff0c;截屏等 WebElement类的方法介绍 文本输入与清除 send_key…

【原创教程】电气电工23:电气柜的品牌及常用型号

电气电工要清楚常用的电气柜品牌及型号,对于电器柜的选择,现在我们一般常用的品牌有3个。分别是好夫满、上海上海桐赛电气和南京巴哈曼电气,还有一种就是网上订制。 一、好夫满系列电气箱 好夫满有很多种类的机箱,EB精巧控制箱系列、KL接线箱系列、BKL不锈钢接线箱系列、…

构建基于 阻塞队列 / 环形队列 的高效生产消费者模型系统

1. 生产者-消费者问题 概述 生产-消费者模型 &#xff1a;一个或多个 生产者线程 产生数据并将其放入共享缓冲区&#xff0c;同时一个或多个 消费者线程 从该缓冲区中读取数据进行操作的情景。 缓冲区 是一个用于存储生产者产生数据的中间容器&#xff1b;缓冲区 的容量通常是…

【操作系统】四、文件管理:1.文件系统基础(文件属性、文件逻辑结构、文件物理结构、文件存储管理、文件目录、基本操作、文件共享、文件保护)

文件管理 文章目录 文件管理八、文件系统基础1.文件的属性2.文件的逻辑结构2.1顺序文件2.2索引文件2.3索引顺序文件2.4多级索引顺序文件 3.目录文件❗3.1文件控制块FCB3.1.1对目录进行的操作 3.2目录结构3.2.1单级目录结构3.2.2两级目录结构3.2.3多级目录结构&#xff08;树形目…