首页 > 编程语言 >C/C++ 思考:策略模式在协议解析中的应用

C/C++ 思考:策略模式在协议解析中的应用

时间:2023-03-29 21:24:48浏览次数:50  
标签:std const string C++ 命令 思考 arg 解析 name

目录

引出问题

在基于消息包的通信协议中,通常会通过一个id或命令名来标识该消息包,程序需要根据不同的标识进行不同的解析策略,提取出想要的内容。例如,一个典型的FTP请求命令是这样的:

USER anonymous\r\n

其中,"USER"是请求命令名,"anonymous"是该命令跟着的参数,"\r\n"是一条FTP命令结尾。

对应FTP请求格式:

命令<SP>参数<CRLF>

传统解析方式

如何从一条FTP请求命令中,解析出命令内容呢?
最直接地,根据空格字符(' ')将请求命令的字符串切分成两部分:命令名、命令参数。然后,将解析出的命令名与已知支持的命令名进行比较,如果匹配上,就调用对应的命令处理程序;如果没匹配上,就调用未知命令异常处理。

于是,我们写出如下代码,对FTP请求命令进行解析:

void handleCommand(const std::string& ftpText)
{
    std::string name; // 命令名
    std::string arg;  // 命令参数
    [name, arg] = str_split(ftpText, ' '); // 自定义切分函数, 将ftpText按空格切分成2个子字符串
    
    if (name == "USER") {
        handleUSER(arg);
    }
    else if (name == "PASS") {
        handlePASS(arg);
    }
    else if (name == "CWD") {
        handleCWD(arg);
    }
    ...
    else {
    // Unkown command
        handleUnknownCommand(arg);
    }
}

策略模式

上面代码能正常运行,但存在2个问题:
1)太繁琐,看起来很糟糕,实际上处理逻辑很简单,都是匹配到命令名就调用对应处理函数;
2)如果要添加对新命令的支持,就要到代码中去添加修改,如果涉及到多个命令,可能出错;

既然都是 “匹配命令 => 调用命令处理程序” 这种模式,能不能将这部分固定下来,而将可能变动的命令名、命令处理函数抽象出来?
答案是可以的,可以用策略模式进行简化。

先复习一下策略模式。

简介

策略模式是一种对象行为模式。

意图:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

适用场景:
1)许多相关的类,仅仅是行为不同,比如一种策略对应一个类。
2)需要使用一个算法的不同变体。
3)算法使用客户不应该知道的数据。
4)一个类定义多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。

策略模式,特别适合用来解决if-else / switch 条件数目不确定的情况,便于用户扩展新的情形。

UML类图

UML类图如下:

其中,

  • Strategy(策略)
    定义所有支持的算法的公共接口。Context用该接口调用某个具体的算法ConcreteStrategy。

  • ConcreteStrategy(具体策略)
    实现Stragegy接口的某个具体算法。

  • Context(上下文)
    使用ConcreteStrategy对象,利用对应算法解决问题的上下文。

改进1:基于函数的代码结构改进

根据策略模式,我们用一个结构体将命令名、命令处理函数用结构体包装起来,然后将支持的命令名、处理函数放到一个数组中。改进代码如下:

typedef struct {
    std::string name;
    void (*handler)(const std::string&);
} FtpCommand_t;

#define COMMANDS_ARRAY_SIZE 10    // 根据实际情况决定数组大小
static FtpCommand_t commands[COMMANDS_ARRAY_SIZE] = {
    {"USER", handleUSER},
    {"PASS", handlePASS},
    {"CWD",  handleCWD},
    ...
};

void handleCommand(const std::string& ftpText)
{
    std::string name; // 命令名
    std::string arg;  // 命令参数
    [name, arg] = str_split(ftpText, ' ');

    for (int i = 0; i < COMMANDS_ARRAY_SIZE; ++i) {
        if (commands[i].name == name) { // 命令名匹配
            commands[i].handler(arg);   // 回调命令处理函数
            break;
        }
    }
}

当要添加、修改或删除一个命令及处理时,就可以直接在支持的命令数组commands[]中修改,而不用修改命令名匹配、回调代码。
注意:这种方式并不会提高程序运行效率,但会让程序结构清晰、易懂,维护更简易。

问题:为何改进中,没有任何策略类,却可称作策略模式?
策略模式的核心,是定义一系列算法,根据不同case,来选择使用不同的、可相互替换的算法。至于每个算法,是用类的形式,还是函数的形式,并非关键。而且,在C++中,函数也可以看作一种特殊的类(函数类)。
上面代码中,每种FTP命令,都对应了一种具体的解析策略,或称算法。策略模式的关键,是根据不同命令名的case,选择不同的解析策略(handlerxxx函数)对FTP请求消息进行解析。

改进2:基于对象的结构改进

C++中,更多时候,处理的代码是类的成员函数,而不是普通函数。
此时,可将前面的结构体数组 转换成一个static vector<> handlers_,然后遍历handleCommand,匹配到命令名就调用对应的命令处理函数即可。
也许,解析FTP消息功能,会包含在像下面FtpSession类中:

class FtpSession
{
public:
    using HandlerFuncType = void (FtpSession::*)(const std::string &);
    using HandlerItemType = std::pair<const std::string, HandlerFuncType>;
    ...
    void handleCommand(const std::string& ftpText);
    
private:
    void handleUSER(const std::string& arg);
    void handlePASS(const std::string& arg);
    void handleCWD(const std::string& arg);
    ...
    static std::vector<HandlerItemType> const handlers_; // 注意handlers_是static属性
};

static成员handlers_,是建立FTP命令名到解析命令的策略的映射的关键:

std::vector<FtpSession::HandlerItemType> const FtpSession::handlers_ = {
        { "USER", &FtpSession::handleUSER },
        { "PASS", &FtpSession::handlePASS },
        { "CWD",  &FtpSession::handleCWD },
        ...
}

策略模式本质,是在上下文中,根据不同case,选择不同算法的策略,对协议进行解析:

void FtpSession::handleCommand(const std::string& ftpText)
{
    std::string name; // 命令名
    std::string arg;  // 命令参数
    [name, arg] = str_split(ftpText, ' ');

    auto item = std::find_if(handlers_.begin(), handlers_.end(), [&name](const HandlerItemType& e) {
        return name == e.first;
    });
    if (item == handlers_.end()) { // fail
        handlerUnknownCommand();
    }
    else {
        auto const hanlder = item->second;
        (this->*handler)(arg); // Callback command handler // 注意这里的this->*handler
    }
}

为什么要用this->*handler进行回调?
因为handlers_.second存放的是void (FtpSession::)(const std::string &),也就是FtpSession成员函数地址,这是一个指针,this->handler表示对当前this对象的成员函数。
通过这种方式,巧妙地将对象的成员通过static成员,转发给当前对象的其他函数。

参考

https://github.com/mtheall/ftpd

标签:std,const,string,C++,命令,思考,arg,解析,name
From: https://www.cnblogs.com/fortunely/p/17270361.html

相关文章

  • c++实战开发程序
    非常感谢您的进一步提问,以下是一个对于实战开发小程序的更具体的建议:第1周实战开发小程序建议:写一个简单的计算器程序,要求包含加、减、乘、除四种基本运算,并进行错误处理......
  • 编写高效C++代码的一些方法
    1.使用基于range的for循环这是C++11中非常酷的特性,如果你想从头到尾迭代,这是最好的方法。usingnamespacestd;intmain(){vector<int>vec={0,1,2,3,4};......
  • C++ ndk编译器及编译脚本
    Gccg++clang编译器的区别GCC、G++和Clang都是常用的编译器,它们有以下区别:编译器的实现:GCC是GNUCompilerCollection的缩写,是由GNU项目开发的一款自由软件,G++是GCC......
  • 75.c++运算符优先级
    优先级运算符结合律助记1::从左至右作用域2a++、a--、type()、type{}、a()、a[]、.、->从左至右后缀自增减、函数风格转型、函数调用、下标、成员......
  • 分布式技术原理与算法解析 04 - 存储&高可靠
    分布式存储分布式数据复制技术常用于数据备份同步复制技术注重一致性,用户请求更新数据库时,主数据库要同步到备数据库后才结束阻塞返回给用户异步复制技术注重可用......
  • C++标准库中的std::nth_leement
    std中的nth_element默认求的是数组中第n小的元素可以通过参数传入,求第n大的元素示例代码#include<algorithm>#include<iostream>#include<vector>usingna......
  • 软考高项第4版教程-差异点解析来啦(第4章)!
    上周已经开始啦!我结合拿到的新版教程,陆续展开的更加深入和细致的解读,帮助你精准把握考试大纲及教程的变化,进而在接下来的考试中旗开得胜!上周带来了第1章和第2章,前天带来了第......
  • DolphinDB StreamEngineParser 解析原理介绍
    DolphinDB曾发布过 DolphinDB:WorldQuant101Alpha因子的流批一体实现 和 DolphinDB:国泰君安191Alpha因子的流批一体实现 这两篇文章,介绍了如何基于DolphinDB的......
  • C++11 笔记
    1、可以利用C++11friend友元新特性创建可靠的测试版本。 2、移动语义 移动构造函数 ----  https://blog.csdn.net/u011852872/article/details/127076918......
  • 思考 React Hook 和 Vue 组合式 API
    Vue组合式API优化周期函数Vue2比如mounted周期中有很多获取数据的逻辑都在这里,在updated周期中又有很多更新的逻辑在这里。在老版本的Vue3文档中讲解组合式AP......