• 设计模式的C语言应用-命令模式-第五章


    模式介绍:命令模式(command)

    命令模式的解释如下:

    向对象发送一个请求,但是并不知道该请求的具体接收者是谁,具体的处理过程是如何的,只知道在程序运行中指定具体的请求接收者即可,对于这样将请求封装成对象的我们称之为命令模式。所以命令模式将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。同时命令模式支 持可撤销的操作。

                  命令模式的C语言实现也是非常显性的。命令发送方不通过直接调用的方式,而是通过发一个命令消息给接收方,让接收方执行操作。C语言里采用命令模式的最常见的原因是核间通信,进程间交互。如果是核间通信,通常是把命令按协定的格式封装在消息数据包里。如果是进程间通信,通常封装成一个结构体,把参数带过去。命令的通道通常是队列。

    命令模式实现

    实现流程

    C语言命令模式经典方式如下,和面向对象是有明显的不同的。下图的invoker表示发命令的实体,而handler表示执行命令的实体,这个和面向对象的命令模式里的含义不一样。

    image.png

    图表 1 C语言命令模式示意图

    image.png

    图表 2面向对象命令模式

    C语言实现的命令模式核心数据结构是命令。发布命令的是invoker,多个invoker将命令封装起来,送到队列里。有一个函数或者线程称为receiver,检查队列里是否有没有处理的命令。由receiver负责调用各个handler。另外一个被经常使用的辅助数据结构是命令码数组,在如果invoker和handler运行于不同的环境,这种做法几乎是必选,如核间通信,内核和应用态通信。命令码作为索引,handler调用函数作为元素,Receiver根据不同的命令码调用handler。

    也有不使用消息队列的C语言实现。

    如果invoker和handler运行于相同的环境,可能直接把handler的回调函数的指针挂在命令结构体上,receiver可以直接调用handler的回调函数。很显然,不同的运行环境是没法这么做的。所以命令码数组是一个更为通用,封装性更好的方法。

    面向对象的命令模式并没有提及到命令的消息队列,也没有提及命令码数组。消息队列本身并不是命令模式的一部分,而是在C语言实现里经常会用到的,特别是命令和执行不再同一个运行环境。命令码数组对于面向对象来说可以用多个子类来实现,所以也不体现出来。

    命令模式的示例代码

    以下代码为伪码。

    命令码的定义

    #define CMD_1   0
    
    #define CMD_2   1
    
    #define CMD_MAX 2

    命令封装结构体

    #define CMD_LEN 256
    
    struct cmd_msg
    
    {
    
        int cmd_code;
    
        char buf[CMD_LEN];//如果是不同环境的,只能用buffer数组,否则可以用指针   
    
    };

    命令的实际处理函数

    typedef int (*cmd_func)(char *buf);
    
    int cmd1_handler(char *buf)
    
    {
    
        return 0;
    
    }
    
    int cmd2_handler(char *buf)
    
    {
    
        return 0;
    
    }

    命令码数组

    命令码数组有两种方式,一种是将命令码作为数据的索引。另外一种情况是由于命令码太大,有一些特殊的规定,没法作为索引。所以在一个结构体里封装命令码和handler,最后实现一个结构体数据,这个在复杂的内核实现里会出现。

    下面是简单的命令码,就是函数指针数组。

    cmd_func cmd_table[] =
    
    {
    
        cmd1_handler,
    
        cmd2_handler,       
    
    };
    
    Invoker和receiver

    Invoker的工作很简单,填充命令命令封装结构体,将其放入队列。

    int invoker1()
    
    {   
    
        struct cmd_msg cmd1_case;
    
        memset(&cmd1_case, 0, sizeof(cmd1_case));
    
        cmd1_case.cmd_code = CMD_1;
    
        //send cmd1_case to queue
    
        return 0;
    
    }
    
    int invoker2()
    
    {   
    
        struct cmd_msg cmd1_case;
    
        memset(&cmd1_case, 0, sizeof(cmd1_case));
    
        cmd1_case.cmd_code = CMD_2;
    
        //send cmd1_case to queue
    
        return 0;
    
    }

    Receiver的工作就是监视命令队列,取出命令调用handler。

    int cmd_receiver()
    
    {
    
        struct cmd_msg *cmd_case;
    
       
    
        while(1)
    
        {
    
            //get cmd_case from queue while queue is not empty 
    
            (*cmd_table[cmd_case->cmd_code])(cmd_case->buf);
    
        }
    
        return 0;
    
    }

    命令队列有很多形态,比如IPC通道,用信号量,也能不要队列直接调用,总之就是让命令交到reciever手上然后分发调用handler。

    伪码main程序:

    int main()
    
    {
    
        invoker1();
    
        invoker2();
    
        cmd_receiver();
    
       
    
        return 0;
    
    }

    内核的实现例子

    内核有非常多的例子,典型的是wireless extension的接口。上层应用通过ioctl下发命令到内核,内核解析后,调用相应的wireless extension内核侧处理函数。这就是典型的不同运行环境的命令模式。参数是buffer,带命令码而不是直接发送函数指针。

    /* -------------------------- IOCTL LIST -------------------------- */
    
    typedef int (*iw_handler)(struct net_device *dev, struct iw_request_info *info,
    
                  void *wrqu, char *extra);
    
    /* Wireless Identification *///命令码
    
    #define SIOCSIWCOMMIT              0x8B00                /* Commit pending changes to driver */
    
    #define SIOCGIWNAME   0x8B01                /* get name == wireless protocol */
    
    #define SIOCSIWNWID     0x8B02                /* set network id (pre-802.11) */
    
    #define SIOCGIWNWID    0x8B03                /* get network id (the cell) */
    
    #define IW_HANDLER(id, func)                                   
    
                  [IW_IOCTL_IDX(id)] = func
    
    //命令码数组
    
    static const iw_handler wl_handler[] =
    
    {
    
                  IW_HANDLER(SIOCSIWCOMMIT, (iw_handler) wireless_commit),
    
                  IW_HANDLER(SIOCGIWNAME, (iw_handler) wireless_get_protocol),
    
                  IW_HANDLER(SIOCSIWFREQ, (iw_handler) wireless_set_frequency),
    
                  IW_HANDLER(SIOCGIWFREQ, (iw_handler) wireless_get_frequency),
    
                  …
    
    }
    
    //典型的receiver
    
    static int ioctl_standard_iw_point(xxx)
    
    {
    
    …
    
    {
    
                  /* Check need for ESSID compatibility for WE < 21 */
    
                  switch (cmd) {
    
                  case SIOCSIWESSID: //没法用索引,所以用了switch case
    
                  case SIOCGIWESSID:
    
                  case SIOCSIWNICKN:
    
                  case SIOCGIWNICKN:
    
                                 if (iwp->length == descr->max_tokens + 1)
    
                                               essid_compat = 1;
    
                                 else if (IW_IS_SET(cmd) && (iwp->length != 0)) {
    
                                               char essid[IW_ESSID_MAX_SIZE + 1];
    
                                               unsigned int len;
    
                                               len = iwp->length * descr->token_size;
    
                                               if (len > IW_ESSID_MAX_SIZE)
    
                                                              return -EFAULT;
    
                                               err = copy_from_user(essid, iwp->pointer, len);
    
                                               if (err)
    
                                                              return -EFAULT;
    
                                               if (essid[iwp->length - 1] == '')
    
                                                              essid_compat = 1;
    
                                 }
    
                                 break;
    
                  default:
    
                                 break;
    
                  }
    
    …
    
    }

    可以看出,由于内核命令码是有特别含义的,所以不能作为索引,只能receiver干脆用switch case。在ioctl_standard_iw_point函数里就是用switch case。

    模式实现总结

    命令模式也是C语言实现的显性的设计模式,角色分为发布命令的invoker,分派命令的receiver和实际执行命令的handler。命令队列和命令码数组是核心的辅助元素。命令码数组目前只有两种类型。命令队列的实现类型就非常多,甚至未必是队列形式,需要设计人员根据经验把握。

    来源:华为云社区  作者:lurayvis

  • 相关阅读:
    《天才源自刻意练习》读书笔记
    IDEA报错,启动失败解决
    Go的定时器之Time.Ticker
    Windows设置硬盘类型
    windows端口转发
    SpringBoot 引入第三方 jar
    【Element】调整 elselect 高度之后 箭头错位问题解决
    AspNetCore&JWT认证授权
    我的微信公众号开通了
    Redis(二):redis持久化
  • 原文地址:https://www.cnblogs.com/2020-zhy-jzoj/p/13165722.html
Copyright © 2020-2023  润新知