【重走编程路】设计模式概述(十) -- 责任链模式、命令模式


前言

行为型模式关注对象之间的交互以及如何分配职责,提供了一种定义对象之间的行为和职责的最佳方式。本章介绍创建型模式中的责任链模式和命令模式。


17. 责任链模式(Chain of Responsibility)

定义

责任链模式为请求的发送者和接收者之间解耦提供了一种松散的耦合方式,使得多个对象都有机会处理这个请求,或者将这个请求传递给链中的下一个对象,直到有一个对象处理它为止。换句话说,这种模式创建了一个接收者对象的链,并沿着这条链传递请求,直到有一个对象处理它为止。

问题

在软件设计中,有时会遇到一个请求可能由多个对象中的任何一个处理,但具体由哪个对象处理在运行时才能确定。如果将请求的发送者与接收者直接绑定,会导致系统难以扩展和维护。

解决方案

责任链模式通过将请求的发送者与接收者解耦,使得请求的发送者不需要知道哪个接收者会处理该请求,同时也使得新的接收者可以很容易地加入到系统中。责任链模式通过以下几个步骤来解决问题:

  1. 定义抽象处理者(Handler)角色:创建一个抽象的类,用于定义处理请求的接口,包含一个指向下一个处理者的引用(通常是同类型的另一个对象)和一个处理请求的方法。
  2. 实现具体处理者(Concrete Handler)角色:实现抽象处理者定义的接口,具体处理它负责的请求,也可以将请求传递给链中的下一个处理者,或者终止请求链。
  3. 组装链:在程序的运行时刻,按照需要将处理者对象组合成链,并明确请求的传递方向。
#include <iostream>  
#include <string>  
  
// 抽象处理者  
class LogHandler {  
protected:  
    LogHandler* nextHandler;  
  
public:  
    void SetNextHandler(LogHandler* nextHandler) {  
        this->nextHandler = nextHandler;  
    }  
  
    virtual void Handle(const std::string& logLevel, const std::string& message) = 0;  
};  
  
// 具体处理者:控制台日志处理器  
class ConsoleLogHandler : public LogHandler {  
public:  
    void Handle(const std::string& logLevel, const std::string& message) override {  
        if (logLevel == "DEBUG" || logLevel == "INFO") {  
            std::cout << "Console: " << message << std::endl;  
        }  
        if (nextHandler != nullptr) {  
            nextHandler->Handle(logLevel, message);  
        }  
    }  
};  
  
// 具体处理者:文件日志处理器  
class FileLogHandler : public LogHandler {  
public:  
    void Handle(const std::string& logLevel, const std::string& message) override {  
        if (logLevel == "INFO" || logLevel == "ERROR") {  
            // 假设有文件写入逻辑  
            std::cout << "File: " << message << std::endl; // 模拟写入文件  
        }  
        if (nextHandler != nullptr) {  
            nextHandler->Handle(logLevel, message);  
        }  
    }  
};  
  
// 客户端代码  
int main() {  
    LogHandler* consoleHandler = new ConsoleLogHandler();  
    LogHandler* fileHandler = new FileLogHandler();  
    consoleHandler->SetNextHandler(fileHandler);  
  
    // 发送日志请求  
    consoleHandler->Handle("DEBUG", "This is a debug message.");  
    consoleHandler->Handle("INFO", "This is an info message.");  
    consoleHandler->Handle("ERROR", "This is an error message.");  
    // 注意:在实际应用中,最好使用智能指针管理动态分配的内存
    delete consoleHandler;
    delete fileHandler;
    return 0;  
}

应用场景

  1. 多个对象可以处理同一请求,但具体由哪个对象处理在运行时动态决定。
  2. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  3. 可动态地增加或删除处理者。

优缺点

优点:

  • 降低耦合度:请求者与接收者之间解耦,提高了系统的灵活性和可扩展性。
  • 简化对象间的连接:对象之间不需要显式地指定请求的接收者,只需要将请求发送到链上即可。
  • 增强系统的动态性:可以在运行时动态地添加或删除处理者。

缺点:

  • 可能导致请求无法处理:如果没有合适的处理者能够处理请求,那么请求可能会在链中一直传递直到链的末尾而得不到处理。
  • 性能问题:请求在链中传递时,需要经过多个处理者的判断和处理,这可能会增加系统的响应时间。特别是当链很长或者每个处理者的处理逻辑较为复杂时,性能问题会更加明显。
  • 调试难度增加:由于请求可能在多个处理者之间传递,因此当出现问题时,定位问题可能会比较困难。

18. 命令模式(Command)

定义

命令模式将一个请求封装为一个对象,从而使你可用不同的请求、队列、日志来参数化其他对象。命令模式也支持可撤销的操作。在命令模式中,命令的发送者和接收者之间是完全解耦的,发送者通过命令对象来间接地调用接收者的相关方法。

问题

在软件设计中,有时需要将请求的发送者和接收者解耦,以便在不改变发送者代码的情况下增加新的接收者,或者在不改变接收者代码的情况下增加新的命令。此外,还需要支持命令的撤销、重做等复杂操作。命令模式通过封装请求为对象的方式,提供了一种灵活的方式来处理这些需求。

解决方案

命令模式通过以下几个步骤来解决问题:

  1. 定义命令接口(Command):创建一个接口,用于声明执行操作的方法。
  2. 具体命令类(Concrete Command):实现命令接口,并关联一个接收者对象,通过调用接收者的方法来执行请求。
  3. 调用者(Invoker):负责调用命令对象的执行方法,它通常持有命令对象的引用。
  4. 接收者(Receiver):知道如何执行命令要求的操作,接收者会执行命令中要求执行的操作。
#include <iostream>  
#include <memory>  
#include <vector>  
  
// 接收者接口  
class Receiver {  
public:  
    virtual void Action() = 0;  
    virtual ~Receiver() {}  
};  
  
// 具体接收者:电视  
class TV : public Receiver {  
public:  
    void Action() override {  
        std::cout << "TV is on." << std::endl;  
    }  
};  
  
// 命令接口  
class Command {  
protected:  
    std::shared_ptr<Receiver> receiver;  
  
public:  
    Command(std::shared_ptr<Receiver> receiver) : receiver(receiver) {}  
    virtual void Execute() = 0;  
    virtual ~Command() {}  
};  
  
// 具体命令:打开电视  
class TVOnCommand : public Command {  
public:  
    TVOnCommand(std::shared_ptr<Receiver> receiver) : Command(receiver) {}  
    void Execute() override {  
        receiver->Action();  
    }  
};  
  
// 调用者:遥控器  
class RemoteControl {  
private:  
    std::vector<std::shared_ptr<Command>> commands;  
  
public:  
    void SetCommand(int slot, std::shared_ptr<Command> command) {  
        if (slot >= 0 && slot < commands.size()) {  
            commands[slot] = command;  
        }  
    }  
  
    void PressButton(int slot) {  
        if (slot >= 0 && slot < commands.size() && commands[slot] != nullptr) {  
            commands[slot]->Execute();  
        }  
    }  
  
    void AddCommand(std::shared_ptr<Command> command) {  
        commands.push_back(command);  
    }  
};  
  
// 客户端代码  
int main() {  
    auto tv = std::make_shared<TV>();  
    auto tvOn = std::make_shared<TVOnCommand>(tv);  
  
    RemoteControl remote;  
    remote.AddCommand(tvOn);  
    remote.AddCommand(nullptr); // 假设第二个槽位不使用  
    remote.PressButton(0); // 输出: TV is on.  
    // remote.PressButton(1); // 如果需要,可以添加更多命令并调用  
    return 0;  
}

应用场景

  1. 需要抽象出待执行的动作,并指定其接收者。
  2. 在不明确指定接收者的情况下,向多个对象中的一个提交请求。
  3. 支持命令的撤销和重做。
  4. 需要保持请求的发送者和接收者之间的解耦。

优缺点

优点:

  • 降低耦合度:命令模式将请求的发送者和接收者解耦,提高了系统的灵活性和可扩展性。
  • 新的命令容易加入:如果需要增加新的命令,只需实现新的命令类即可,无需修改现有代码。
  • 支持撤销和重做:通过扩展命令模式,可以容易地实现命令的撤销和重做功能。

缺点:

  • 类爆炸:在命令模式中,每个具体的命令类都可能对应一个接收者类中的方法。如果系统中有很多这样的方法,那么就需要创建大量的具体命令类,这可能会导致系统中类的数量激增,增加系统的复杂性。
  • 系统复杂度增加: 命令模式增加了系统的抽象层次,使得系统的设计和实现变得更加复杂。对于简单的系统来说,使用命令模式可能会带来不必要的开销和复杂性。
  • 性能问题:由于命令模式涉及到多个类的实例化和方法调用,因此可能会带来一定的性能开销。

To be continued.

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值