一雨田的专栏

伟人将复杂的事情变简单,小人将简单的事情变复杂

用户操作
[即时聊天] [发私信] [加为好友]
一雨田ID:dylgsy
91678次访问,排名1090,好友2人,关注者13人。
一雨田
dylgsy的文章
原创 41 篇
翻译 1 篇
转载 6 篇
评论 292 篇
最近评论
heray818:我的邮箱是:heray818@126.com
heray818:你好 能否也传我一份 谢谢了啊
救援隊募集:アダルトエロ不倫
モテ度審査員:童貞セフレナンパ
temp:很好
文章分类
收藏
    相册
    好友Blog
    圈内朋友
    sankt的专栏
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 实现自己的“命令映射表”(上)收藏

    新一篇: 实现自己的“命令映射表”(下) | 旧一篇: UML类图关系全面剖析

    问题是这样的,在自己的一个程序里,需要根据接收到的命令(此命令是一个字串)来执行相应的函数。类似的情况可能有:

    1、SOCKET 程序:程序可能要根据对方传来的命令来执行相应的函数。
    2、脚本解析程序:在一个程序中根据读入的命令串来执行相应的函数。
    3、类似WINDOWS的消息系统,一条消息对应一个函数。

    反正这种的应用很多,学会这种设计手法是非常有帮助的,因为它可以帮我们轻松的添加新的命令。这种手法也很象MFC 中的消息映射表,但我们当然没必要搞得那么复杂。只要能适合我们的应用就行。好,下面让我们开始吧。

    我们想一下,一般碰到这种情况,我们会怎么做。我一般会这样写:

    // 注意这里是类成员函数,示例代码仅供参考

    // 读脚本
    void CParser::GetCmd(CCommandList &CmdList)
    {
     CString strCmd;
     while(!CmdList.End())
     {
      strCmd = CmdList.GetNextCmd();
      ExecCmd(strCmd);
     }
    }

    // 执行脚本
    void CParser::ExecCmd(CString strCmd)
    {
     if(strCmd == "#a")
      MethodA();
     if(strCmd == "#b")
      MethodB();
        ...
    }

    这种方法其实也没问题,只不过如果要增加新的命令就要在这个函数后面加,弄不好这个函数就会越来越长了(想一想WINDOWS成百上千的消息)。函数越来越长,

    人就会看得好晕,好晕的时候就会出错,要知道,程序的错误大多数是由于人的失误造成的。所以下面是另一种可选的方法:

    1、在类的头文件里定义一个map,把命令和函数对应起来,如下(省略了无关的代码)

    #pragma warning(disable: 4786)
    #include <map>
    using namespace std;

    class CParser
    {
    public:
     void GetCmd(CCommandList &CmdList);
     void ExecCmd(CString strCmd);

     // 响应命令表
     typedef void (CParser::*FuncPointer)(void);
     map<CString, FuncPointer> m_cmdMap;

     // 响应函数
     void MethodA();
     void MethodB();
    };

    2、在CPP文件里建表,注意写法
    CParser::CParser()
    {
     m_cmdMap["#a"] = (FuncPointer)&CParser::MethodA;
     m_cmdMap["#b"] = (FuncPointer)&CParser::MethodB;
    }

    在这里我们还可以学习MFC的做法,定义一个宏:
    #define ON_CMD(a, b) ( m_cmdMap[(a)] = (FuncPointer)&CParser::b )

    这样的话,我们的建表就更方便了,如下:
    CParser::CParser()
    {
     ON_CMD("#a", MethodA);
     ON_CMD("#b", MethodB);
    }

    3、修改一下ExecCmd

    // 执行脚本
    void CParser::ExecCmd(CString strCmd)
    {
     if(m_cmdMap.find(strCmd) != m_cmdMap.end())
     {
      FuncPointer func;

      // 取得对应函数
      func = m_cmdMap[strCmd];

      // 请特别注意这里的写法!!!
      // 调用函数
      (this->*func)(); 
     }
     else
     {
      CString strMsg;
      strMsg.Format("找不到命令:\"%s\" 所对应的函数", strCmd);
      OutMsg(strMsg);
     }  
    }

    这样做我们就实现了自己的命令响应表了。

    使用这种方法,好处如下:
    1、可以代替上述的if-else、switch-case(例如WINDOWS程序)、虚函数(要知道,这种if-else是可以用多态来代替的)等方法。可参考《代替if-else,switch-case的几种方法》。
    2、可以方便添加命令,添加时候不会影响原来的函数,符合OCP原则。
    3、各个处理函数分开,不会互相影响,就算一个函数写错了,也不会“一只蚊子搞臭了一锅粥”,把错误的范围控制住了。

    可能看到这里,有些人还要问如果参数不同怎么办?这个问题我们在下一篇再讨论。

     

    发表于 @ 2006年09月20日 17:52:00|评论(loading...)|编辑

    新一篇: 实现自己的“命令映射表”(下) | 旧一篇: UML类图关系全面剖析

    评论

    #William Ni 发表于2006-09-21 14:54:00  IP: 61.173.159.*
    垃圾程序 没有丝毫的参考价值
    #w8u 发表于2006-09-21 16:04:00  IP: 152.104.237.*
    为什么不拿着命令字符串 , 主动询问命令处理器,你可以处理这个命令不?
    #一雨田 发表于2006-09-21 15:33:00  IP: 211.156.179.*
    这个方法应该能帮到你一些忙吧,William兄!

    我在网上找了很久,包括国内和国外的,也没找到完整的答案,比较完善的一篇是侯捷写的(http://jjhou.csdn.net/article98-20.htm)。但是也没说到在类中如何使用这种方法。因为我的命令映射表函数要调用类的一些变量。

    这篇文章是我根据看过的资料和实践总结出来的,上下篇都比较完整的说明了“命令映射表”的设计,而且也应用到我的程序中去,证明是有效的。只不过上面没给出完整的程序,但是我觉得本文章已经能把设计思路表达清楚了。

    如果需要,我可以把一个例子程序EMail给你(因为完整程序是公司的项目代码)。

    William兄觉得是垃圾程序,很可能是你没想过你的程序可以这样去写。
    #一雨田 发表于2006-09-21 16:23:00  IP: 211.156.179.*
    w8u:
    首先,程序怎么样实现都行,只要你能把功能实现了。
    你说的这种方法在实现上应该是这样的。你所说的命令处理器实际上应该是一个函数。例如:BOOL CmdHandle(CString &strCmd)。

    我拿着命令字符串去询问,实际上就是把命令字符串传进去,然后CmdHandle就做处理,如果能处理就处理了,并返回TRUE,如果不能处理就返回FALSE。

    那这个CmdHandle内部是怎么样的呢?应该就是if-else或者switch-case吧。那就回复到我最开始说的那样,这样的代码,要增加一条新的命令怎么办?就要改动这个CmdHandle了,而且要注意,添加每个命令都要改动它,那长时间下来,程序经过N个程序员的手,这个函数就会变得很长和很恐怖了。

    所以,我觉得还是这种“命令映射表”的一个命令对应一个函数这种做法比较舒服。至少我以后添加任何代码,我不会影响原来的代码。OCP原则就是这样的。把不变化的封装起来,把变化的开放!
    #w8u 发表于2006-09-21 17:14:00  IP: 152.104.237.*
    你理解错误了我的意思。下面的代码没有仔细思考,也没有实践检验。

    1.定义命令处理器接口
    class ICmdHandler
    {
    public:
    virtual HRESULT Handle(string cmd)=0;
    };

    2.一个具体的命令处理器
    class CACmdHandler : public ICmdHandler
    {
    public:
    CACmdHandler(...)
    ...

    public:

    HRESULT Handle(string cmd)
    {
    if( cmd != "#A" ) return 不认识的命令
    else
    {
    处理命令
    ....

    }
    return S_OK;
    }
    }

    3.新增命令只需要继承 ICmdHandler即可。

    4.命令处理器队列。
    list<ICmdHandler* pHandler> cmdlist;

    将所有的命令处理器实例丢进队列去。如果新增了一个命令,修改仅仅需要增加队列元素。


    5.执行命令
    for each cmdHandler in cmdList
    if ( cmdHandler.Handle( cmdString ) == S_OK ) break;
    #一雨田 发表于2006-09-21 17:49:00  IP: 211.156.179.*

    to w8u:

    这种方法是一个好方法,是利用了多态来隔离变化。增加新的命令时只需要派生出一个新的命令类,然后在某个地方增加队列元素就行。

    这个方法客观来说,比我的方法更面向对象,但可能存在着效率方面的问题,因为多态必然涉及到虚表。如果很多命令的话,可能会在时间或者空间效率上存在

    问题(我的程序不涉及太多的派生层,可能时间上就没什么问题了,但是一个类要维护一个虚表,可能会存在空间上的效率问题),就像MFC不采用多态一样。还有就是多一个命令就要派生一个类,类可能会很多。 不过对于大部分的应用来说都可行了,我的程序应该也可行。

    谢谢你提出的这种思路,欢迎讨论!

    这里想给你出个题,能否用你的方法实现不同类型的函数调用呢(就像我的下篇一样)。呵呵,我知道你一定行,想看看你的做法。
    #hacker47 发表于2006-09-21 20:52:00  IP: 220.179.181.*
    建议你看看编译原理吧,兄弟。
    #好文! 发表于2006-09-21 22:44:00  IP: 218.246.97.*
    好文!
    期待下篇
    #ymhui 发表于2006-09-23 14:41:00  IP: 222.95.232.*
    非常讨厌William Ni 之流,说别人是垃圾,却不说别人为什么是垃圾。这样做,实在是很不负责任。
    只能说他是自视清高。

    命令映射表的上下文我都看了,写得非常不错。特别是下篇,很象MFC。支持支持阿。
    #一雨田 发表于2006-09-25 15:01:00  IP: 211.156.179.*
    谢谢支持! :)
    #dnglee 发表于2006-09-26 13:02:00  IP: 219.159.83.*
    这是一种常用的编程方法,名字叫“表驱动”,在《代码大全》中有介绍。
    #ildy 发表于2006-09-29 17:12:00  IP: 202.118.4.*
    哪有《代替if-else,switch-case的几种方法》啊
    #KAKA 发表于2006-10-16 18:14:00  IP: 58.246.87.*
    是啊,提到的《代替if-else,switch-case的几种方法》文章在那里?
    #一雨田 发表于2006-10-18 15:43:00  IP: 211.156.179.*
    不好意思,很久没空来了,这篇文章新写完了,在这里:
    http://blog.csdn.net/dylgsy/archive/2006/10/18/1339688.aspx
    #a_chuan 发表于2008-09-26 10:21:45  IP: 123.116.158.*
    不错,不错
    发表评论  


    登录
    Csdn Blog version 3.1a
    Copyright © 一雨田