【重走编程路】设计模式概述(十二) -- 访问者模式、中介者模式、解释器模式

文章目录

  • 前言
  • 21. 访问者模式(Visitor)
    • 定义
    • 问题
    • 解决方案
    • 应用场景
    • 优缺点
  • 22. 中介者模式(Mediator)
    • 定义
    • 要解决的问题
    • 解决方案
    • 应用场景
    • 优缺点
  • 23. 解释器模式(Interpreter)
    • 定义
    • 解决方案
    • 应用场景
    • 优缺点


前言

行为型模式关注对象之间的交互以及如何分配职责,提供了一种定义对象之间的行为和职责的最佳方式。本章介绍创建型模式中的访问者模式、中介者模式和解释器模式。


21. 访问者模式(Visitor)

定义

访问者模式是一种将数据操作与数据结构分离的设计模式。它定义了一个作用于某对象结构中的各元素的操作,它可以在不修改各元素的类的前提下定义作用于这些元素的新操作。访问者模式使得用户可以在不修改现有类层次结构的情况下,增加新的操作。

问题

在软件设计中,经常需要在不修改现有类结构的情况下,为对象结构中的元素添加新的操作。如果直接在元素类中增加新的方法,会违反开闭原则(对扩展开放,对修改关闭),因为每增加一个新的操作,都需要修改所有元素类。

解决方案

访问者模式通过将操作封装在访问者类中,并将接受访问者访问的元素类设计为可接受访问者访问的接口,从而实现操作的增加不依赖于元素类的修改。具体地,访问者模式定义了一个访问者接口,该接口声明了所有要作用于元素类上的操作;同时,每个元素类都包含一个接受访问者对象的accept方法,该方法将访问者对象作为参数传入,并调用访问者对象中的相应方法来执行操作。

#include <iostream>
#include <string>class Cat;
class Dog;// 访问者接口
class AnimalVisitor {
public:virtual void Visit(Cat& cat) = 0;virtual void Visit(Dog& dog) = 0;virtual ~AnimalVisitor() {}
};// 具体访问者:喂食
class FeedVisitor : public AnimalVisitor {
public:void Visit(Cat& cat) override {std::cout << "Feeding cat with fish." << std::endl;}void Visit(Dog& dog) override {std::cout << "Feeding dog with bone." << std::endl;}
};// 动物基类,包含接受访问者的方法
class Animal {
public:virtual void Accept(AnimalVisitor& visitor) = 0;virtual ~Animal() {}
};// 具体动物类:猫
class Cat : public Animal {
public:void Accept(AnimalVisitor& visitor) override {visitor.Visit(*this);}
};// 具体动物类:狗
class Dog : public Animal {
public:void Accept(AnimalVisitor& visitor) override {visitor.Visit(*this);}
};int main() {Cat cat;Dog dog;FeedVisitor feeder;cat.Accept(feeder);dog.Accept(feeder);return 0;
}

应用场景

  1. 当一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
  2. 需要对对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。
  3. 当对象结构中的对象经常被修改,但调用这些对象的操作却不应该被修改时。

优缺点

优点:

  • 增加新的操作很容易:无需修改现有的类层次结构,只需要增加一个新的访问者类即可。
  • 将有关的行为集中到一个访问者对象中:使得相关的操作更加容易理解和维护。
  • 灵活性:可以在运行时动态地改变对象的行为。

缺点:

  • 增加新的元素类时较为困难:每增加一个新的元素类,都需要修改访问者接口,增加一个新的访问方法。
  • 破坏封装:访问者可以访问并修改元素的状态,这可能会破坏元素的封装性。
  • 性能问题:如果访问者对象访问的元素非常多,那么访问者的效率可能会成为问题。

22. 中介者模式(Mediator)

定义

中介者模式定义了一个中介对象来封装一系列对象之间的交互,使得各个对象之间不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

要解决的问题

在软件系统中,对象之间的直接交互可能会导致类的职责过多,进而增加代码的复杂性和维护难度。特别是当多个对象之间存在复杂的交互关系时,如果它们直接相互引用和通信,会使得系统的结构变得混乱,难以理解和维护。

解决方案

中介者模式通过引入一个中介者对象来管理对象之间的交互,各个对象通过中介者对象来间接地与其他对象通信。这样,对象之间不再需要显式地相互引用,降低了它们之间的耦合度,使得系统更加灵活和易于维护。

#include <iostream>  
#include <vector>  
#include <string>  // 聊天者接口  
class Chatter {
public:virtual ~Chatter() {}virtual void Send(const std::string& message) = 0;virtual void Receive(const std::string& from, const std::string& message) = 0;virtual std::string GetName() const = 0;
};// 中介者接口  
class ChatMediator {
public:virtual ~ChatMediator() {}virtual void RegisterChatter(Chatter* chatter) = 0;virtual void SendMessage(Chatter* sender, const std::string& message) = 0;
};// 聊天者实现
class User : public Chatter {
private:std::string name;ChatMediator* mediator;public:User(const std::string& name, ChatMediator* mediator) : name(name), mediator(mediator) {}void Send(const std::string& message) override {mediator->SendMessage(this, message);}void Receive(const std::string& from, const std::string& message) override {std::cout << from << ": " << message << std::endl;}std::string GetName() const override {return name;}
};// 中介者实现  
class ConcreteChatMediator : public ChatMediator {
private:std::vector<Chatter*> chatters;public:void RegisterChatter(Chatter* chatter) override {chatters.push_back(chatter);}void SendMessage(Chatter* sender, const std::string& message) override {for (auto chatter : chatters) {if (chatter != sender) {chatter->Receive(sender->GetName(), message);}}}
};int main() {ConcreteChatMediator mediator;User alice("Alice", &mediator);User bob("Bob", &mediator);mediator.RegisterChatter(&alice);mediator.RegisterChatter(&bob);alice.Send("Hello, Bob!");bob.Send("Hi, Alice. How are you?");return 0;
}

应用场景

  1. 聊天室系统:中介者负责转发各个聊天者之间的消息。
  2. MVC框架:控制器(Controller)作为中介者,负责接收用户的输入,并调用模型和视图来完成相应的业务逻辑和界面更新。
  3. 事件处理系统:中介者负责管理和分发事件,各个事件监听者通过中介者来接收和处理事件。

优缺点

优点:

  • 降低耦合度:通过中介者对象来管理对象之间的交互,降低了对象之间的耦合度。
  • 易于维护:由于减少了对象之间的直接引用,系统的结构更加清晰,易于理解和维护。
  • 灵活:可以独立地改变对象之间的交互方式,而不需要修改对象的代码。

缺点:

  • 中介者可能会变得复杂:如果系统中对象之间的交互非常复杂,中介者对象可能会变得庞大和难以维护。
  • 增加了中介者类的负担:所有对象之间的交互都需要通过中介者来进行,这可能会增加中介者类的负担。

23. 解释器模式(Interpreter)

定义

解释器模式定义了一个语言的文法,并构建一个解释器来解释这个语言中的句子。这种模式允许程序通过定义一套规则(即文法)来解释一种特定类型的表达式或语句。在解释器模式中,每个表达式的组成部分都被表示为一个类,这些类共同工作来解析表达式。

解决方案

解释器模式通常使用递归的方式来实现表达式的解析,每个非终结符表达式都依赖于其他表达式来解析其组成部分。可以将语句表示为抽象语法树,然后通过解释器逐步执行和解释这个语法树。
解释器模式主要围绕以下几个角色和组件来构建:

  1. 抽象表达式(Abstract Expression): 声明一个抽象的解释操作,该操作由具体的表达式角色实现。
  2. 终结符表达式(Terminal Expression): 实现与文法中的终结符相关的操作,一个终结符是文法中最基本的单位,它不可再分。
  3. 非终结符表达式(Non-terminal Expression): 为文法中的非终结符实现解释操作,非终结符需要进一步的解释。
  4. 环境(Context): 包含解释器之外的全局信息,如变量值等。
#include <iostream>  
#include <map>  
#include <stack>  
#include <string>  // 抽象表达式  
class Expression {
public:virtual int Interpret(const std::map<char, int>& vars) = 0;virtual ~Expression() {}
};// 变量表达式  
class VarExpression : public Expression {
private:char var;
public:VarExpression(char var) : var(var) {}int Interpret(const std::map<char, int>& vars) override {return vars.at(var);}
};// 抽象运算表达式  
class SymbolExpression : public Expression {
protected:Expression* left;Expression* right;public:SymbolExpression(Expression* left, Expression* right): left(left), right(right) {}~SymbolExpression() {delete left;delete right;}
};// 加法表达式  
class AddExpression : public SymbolExpression {
public:AddExpression(Expression* left, Expression* right): SymbolExpression(left, right) {}int Interpret(const std::map<char, int>& vars) override {return left->Interpret(vars) + right->Interpret(vars);}
};// 减法表达式  
class SubExpression : public SymbolExpression {
public:SubExpression(Expression* left, Expression* right): SymbolExpression(left, right) {}int Interpret(const std::map<char, int>& vars) override {return left->Interpret(vars) - right->Interpret(vars);}
};// 表达式解析器  
class Calculator {
private:Expression* root;public:Calculator(const std::string& exp) {std::stack<Expression*> stack;for (char c : exp) {if (std::isdigit(c) || (c >= 'a' && c <= 'z')) {stack.push(new VarExpression(c));}else if (c == '+') {Expression* right = stack.top();stack.pop();Expression* left = stack.top();stack.pop();stack.push(new AddExpression(left, right));}else if (c == '-') {Expression* right = stack.top();stack.pop();Expression* left = stack.top();stack.pop();stack.push(new SubExpression(left, right));}}root = stack.top();stack.pop();}~Calculator() {delete root;}int Run(const std::map<char, int>& vars) {return root->Interpret(vars);}
};// 主函数,用于测试计算器  
int main() {// 使用逆波兰表示法输入计算式Calculator calc("ab+c-");// 定义变量值  std::map<char, int> vars = { {'a', 1}, {'b', 2}, {'c', 1} };// 执行计算  int result = calc.Run(vars);// 输出结果  std::cout << "Result: " << result << std::endl;return 0;
}

应用场景

解释器模式适用于以下场景:

  1. 需要将一个语言中的句子表示为一个抽象语法树(AST)时。
  2. 存在大量重复出现的表达式,这些表达式可以用一种简单的语言来表达。
  3. 当一种语言需要解释执行,并且这种语言可以通过构建抽象语法树来轻松扩展时。

例如,编译器设计、表达式计算器、正则表达式解析器、机器人指令解析等场景都适合使用解释器模式。

优缺点

优点:

  • 灵活性: 解释器模式使得语言易于扩展,因为新的表达式类型可以简单地通过添加新的类来实现。
  • 可重用性: 解释器模式可以重用语法和表达式对象,特别是在处理相似类型的表达式时。

缺点:

  • 复杂性: 对于复杂的文法,解释器模式可能会导致大量的类和接口,使得系统难以理解和维护。
  • 性能问题: 递归调用和大量的对象创建可能会导致性能问题,特别是在处理大量数据时。
  • 难以调试: 由于表达式的解析过程可能涉及多个类和方法的调用,因此调试可能变得复杂。

The end

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

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

相关文章

数据仓库事实表

数据仓库中的三种常见事实表类型&#xff1a;事务事实表、周期快照事实表和累积快照事实表 事务事实表&#xff1a; 事务事实表是记录事务级别数据的事实表。它记录了每个事务发生的具体度量指标&#xff0c;如销售金额、数量等。事务事实表的优势在于能够提供详细的事务级别…

四个节点即可实现的ComfyUI批量抠图工作流

原文链接&#xff1a;ComfyUI面部修复完全指南 (chinaz.com) 下图就是批量抠图的工作流 虽然工作流很简单&#xff0c;但是我们前提还是需要安装好我们的节点 首先安装我们的抠图节点 安装 BiRefNet 所需依赖&#xff1a;timm&#xff0c;如已安装无需运行 requirements.txt…

苹果电脑crossover怎么下载 苹果电脑下载crossover对电脑有影响吗 MacBook下载crossover软件

CodeWeavers 发布了 CrossOver 24 版本更新&#xff0c;不仅兼容更多应用和游戏&#xff0c;得益于 Wine 9.0 带来的 7000 多项改进&#xff0c;CrossOver 还可以在 64 位系统上运行Windows应用的软件&#xff0c;使得用户可以在Mac系统中轻松安装使用仅支持Windows系统运营环境…

java设计模式:03-04-装饰器模式

装饰器模式&#xff08;Decorator Pattern&#xff09; 装饰器模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其结构。装饰器模式通过创建一个装饰类来包装原有的类&#xff0c;…

视觉探秘:sklearn中聚类标签的可视化之道

视觉探秘&#xff1a;sklearn中聚类标签的可视化之道 在数据科学领域&#xff0c;聚类分析是一种无监督学习方法&#xff0c;用于将数据集中的样本划分为若干个组或“簇”&#xff0c;使得同一组内的样本相似度高&#xff0c;而不同组之间的样本相似度低。Scikit-Learn&#x…

如何在Java、Python、PHP中使用短信推广API?

短信推广API是一种用于营销和推广目的的应用程序接口。该API允许开发者通过短信通道向目标受众发送推广信息&#xff0c;包括促销活动、产品介绍、特价优惠等。短信推广API通常支持群发功能、链接跟踪、定时发送等特性&#xff0c;以满足企业对于推广活动的灵活需求&#xff0c…

VScode如何进行调试

参考资料: VS Code入门教程2020 #24 介绍launch.json launch.json配置文件 {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访问: https://go.microsoft.com/fwlink/?linkid830387"version": "0.2.0&qu…

Python中的私有属性和方法

在Python编程中&#xff0c;封装是一种重要的面向对象特性&#xff0c;用于限制对类内部数据和方法的访问&#xff0c;确保对象的内部状态只能通过特定的方法进行修改。这种封装机制通过使用私有属性和方法来实现。本文将详细介绍Python中的私有属性和方法及其实现方式。 什么…

搜维尔科技:【研究】动作捕捉加速游戏开发行业的发展

动作捕捉加速游戏开发行业的发展 Sunjata 的故事始于 2004 年&#xff0c;它将席卷乌干达视频游戏行业&#xff0c;然后席卷全世界。但首先&#xff0c;Klan Of The Kings 的小团队需要工具来实现他们的愿景。 漫画家兼非洲民间传说爱好者罗纳德卡伊马 (Ronald Kayima) 在将…

idea navigate mysql生成实体类

参考&#xff1a;https://blog.51cto.com/u_16175427/7251120 使用idea导航MySQL生成实体类 1、在IDEA的顶部菜单中选择View -> Tool Windows -> Database 2、找到表&#xff0c;右键表Scripted Extensions -> Generate POJO...

定个小目标之刷LeetCode热题(45)

32. 最长有效括号 给你一个只包含 ( 和 ) 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号 子串的长度。 示例 1&#xff1a; 输入&#xff1a;s "(()" 输出&#xff1a;2 解释&#xff1a;最长有效括号子串是 "()"有事…

6. dolphinscheduler-3.0.0伪集群部署

环境说明&#xff1a; 主机名&#xff1a;cmc01为例 操作系统&#xff1a;centos7 安装部署软件版本部署方式centos7zookeeperzookeeper-3.4.10伪分布式hadoophadoop-3.1.3伪分布式hivehive-3.1.3-bin伪分布式clickhouse21.11.10.1-2单节点多实例dolphinscheduler3.0.0单节…

ELK kibana查询与过滤

ELK kibana查询与过滤 1、通过布尔操作符 AND 、 OR 和 NOT 来指定更多的搜索条件(注意&#xff1a;这AND、OR、NOT必须大写)。例如&#xff0c;搜索message包含服务层关键词并且日志级别为INFO的条目&#xff0c;您可以输入 message:“服务层” AND level:“INFO”。 2、要搜…

Qt 实战(7)元对象系统 | 7.4、属性系统:深度解析与应用

文章目录 一、属性系统&#xff1a;深度解析与应用1、定义属性2、属性系统的作用3、属性系统工作原理&#xff08;1&#xff09;Q_PROPERTY宏&#xff08;2&#xff09;moc 的作用&#xff08;3&#xff09;属性在元对象中的注册 4、获取与设置属性4.1、QObject::property()与Q…

electron定义的变量,vue调用

在 Electron 中&#xff0c;主进程和渲染进程之间是通过 Inter-Process Communication (IPC) 机制进行通信的。这意味着在主进程中定义的变量不能直接在 Vue&#xff08;渲染进程&#xff09;中访问&#xff0c;因为它们运行在不同的隔离环境中。 要在 Vue 中访问主进程中定义…

KU FPGA FLASH boot失败debug

原因 新板子回来后&#xff0c;测试flash 烧录正常&#xff0c;但是无法BOOT&#xff0c;此时SPI设置为X4模式,使用内部时钟&#xff0c;速度90M。烧录过程不报错&#xff0c;校验也正常。 FLASH理论支持最大速度108M&#xff0c;90M应该还好。另外板卡预留了EMCCLK外部时钟模…

Python+Flask+MySQL/Sqlite的个人博客系统(前台+后端管理)【附源码,运行简单】

PythonFlaskMySQL/Sqlite的个人博客系统&#xff08;前台后端管理&#xff09;【附源码&#xff0c;运行简单】 总览 1、《个人博客系统》1.1 方案设计说明书设计目标工具列表 2、详细设计2.1 管理员登录2.2 程序主页面2.3 笔记新增界面2.4 文章新增界面2.5 文章/笔记管理界面2…

分享一个 .NET EF 6 扩展 Where 的方法

前言 Entity Framework 6&#xff08;EF 6&#xff09;中的 Where 方法用于筛选数据库中的数据并返回符合条件的结果&#xff0c;但 Where 方法只能进行简单的筛选条件&#xff0c;例如相等、大于、小于等简单条件&#xff0c;如果需要处理更复杂的逻辑条件&#xff0c;则需要…

iMazing 3 换手机后苹果游戏数据还有吗 换iPhone怎么转移游戏数据

当你想要更换手机&#xff0c;无论是选择升级到最新款iPhone&#xff0c;或者换到“经典”旧款iPhone&#xff0c;单机游戏数据的转移总是让人发愁。本文将详细介绍换手机后苹果游戏数据还有吗&#xff0c;以及换iPhone怎么转移游戏数据&#xff0c;确保你能无缝继续你的游戏体…

力扣第十七题——电话号码的字母组合

内容介绍 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "23" 输出…