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

文章目录


前言

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


21. 访问者模式(Visitor)

定义

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

问题

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

解决方案

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

c 复制代码
#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)

定义

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

要解决的问题

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

解决方案

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

c 复制代码
#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): 包含解释器之外的全局信息,如变量值等。
c 复制代码
#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

相关推荐
思忖小下3 小时前
梳理你的思路(从OOP到架构设计)_简介设计模式
设计模式·架构·eit
liyinuo20175 小时前
嵌入式(单片机方向)面试题总结
嵌入式硬件·设计模式·面试·设计规范
aaasssdddd967 小时前
C++的封装(十四):《设计模式》这本书
数据结构·c++·设计模式
T1an-17 小时前
设计模式之【观察者模式】
观察者模式·设计模式
思忖小下9 小时前
梳理你的思路(从OOP到架构设计)_设计模式Factory Method模式
设计模式·工厂方法模式·eit
霁月风10 小时前
设计模式——工厂方法模式
c++·设计模式·工厂方法模式
发飙的蜗牛'12 小时前
23种设计模式
android·java·设计模式
NorthCastle1 天前
设计模式-创建型模式-简单工厂模式详解
设计模式·简单工厂模式
越甲八千1 天前
重拾设计模式-外观模式和适配器模式的异同
设计模式·适配器模式·外观模式
越甲八千1 天前
重拾设计模式--适配器模式
设计模式·适配器模式