首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件开发 >

【原创&交流】Comamnd形式和Factory模式在一次代码重构中的应用

2012-09-23 
【原创&交流】Comamnd模式和Factory模式在一次代码重构中的应用如何应用设计模式,是一个见仁见智的问题,可能

【原创&交流】Comamnd模式和Factory模式在一次代码重构中的应用
如何应用设计模式,是一个见仁见智的问题,可能也没有一定之规。以我的水平也谈得不一定好。前段时间重构了公司软件中的二维图形交互方面的代码,总结了一些经验供大家分享,期待起一个抛砖引玉的作用。

  该软件是基于MFC界面框架开发的。MFC的Doc-View架构本质上是一种MVC架构。随着软件功能开发越来越多,图形的二维显示及各种浏览命令、编辑命令的实现集中在视图类(即View)。视图类的代码越来用臃肿(将近上万行的代码),它的维护更新和升级以后都将大成问题。一个例证就是在各种键盘鼠标消息响应函数写无数的case分支语句来处理各种命令,类似于:

 

C/C++ code
void CMyView::OnLButtonDown(){      switch(m_Cmd)     {         case Cmd_Browse:            {               ……               break;            }         case Cmd_Draw_Line:            {               ……               break;             }                default:               break;       }}      


  毫无疑问,视图类已经趋向于成为一个上帝类(注意这里说的上帝类不是无所不能的类,而是只有上帝才懂的类)。

  我已经闻到了一股代码发臭的味道.如何改造上帝类,成为我面临的一个问题。改造上帝类,不是简单的拆分代码那么简单。以前我看过一个系统的代码,里面视图类的代码也很多,然后就将视图类的一个cpp文件分为两个cpp文件。这就是一个简单拆分的例子,却无助于提升代码的可维护性和系统的可扩展性。

恰好在这个时候我阅读了微软的VS的画图示例drawcli的代码。这个示例给了我启发。在下面我将介绍drawcli的做法以及在学习它的基础上我的做法。

  Drawcli的功能简单,主要是实现一些简单图形的绘制和编辑,如下图:



  (红色画框为它的画图工具栏)
   
  那么Drawcli是如何设计和实现它的绘图功能呢?视图类为CDrawView,我们看它是如何响应各种鼠标消息的:
 
C/C++ code
    void CDrawView::OnLButtonDown(UINT nFlags, CPoint point){    if (!m_bActive)        return;    CDrawTool* pTool = CDrawTool::FindTool(CDrawTool::c_drawShape);    if (pTool != NULL)        pTool->OnLButtonDown(this, nFlags, point);}void CDrawView::OnLButtonUp(UINT nFlags, CPoint point){    if (!m_bActive)        return;    CDrawTool* pTool = CDrawTool::FindTool(CDrawTool::c_drawShape);    if (pTool != NULL)        pTool->OnLButtonUp(this, nFlags, point);}void CDrawView::OnMouseMove(UINT nFlags, CPoint point){    if (!m_bActive)        return;    CDrawTool* pTool = CDrawTool::FindTool(CDrawTool::c_drawShape);    if (pTool != NULL)        pTool->OnMouseMove(this, nFlags, point);}


  我们看到CDrawView的鼠标消息响应函数中并没有具体实现,它是通过查找一个工具类CDrawTool,然后调用它的成员函数来实现的功能。我们再看看CDrawTool的代码:

 
C/C++ code
// 图形类型枚举变量enum DrawShape{    selection,    line,    rect,    roundRect,    ellipse,    poly};// 绘制工具基类class CDrawTool{// Constructorspublic:    CDrawTool(DrawShape nDrawShape);// Overridables    virtual void OnLButtonDown(CDrawView* pView, UINT nFlags, const CPoint& point);    virtual void OnLButtonDblClk(CDrawView* pView, UINT nFlags, const CPoint& point);    virtual void OnLButtonUp(CDrawView* pView, UINT nFlags, const CPoint& point);    virtual void OnMouseMove(CDrawView* pView, UINT nFlags, const CPoint& point);    virtual void OnEditProperties(CDrawView* pView);    virtual void OnCancel();// Attributes    DrawShape m_drawShape;    static CDrawTool* FindTool(DrawShape drawShape);    static CPtrList c_tools;    static CPoint c_down;    static UINT c_nDownFlags;    static CPoint c_last;    static DrawShape c_drawShape;};// 绘制矩形类class CRectTool : public CDrawTool{// Constructorspublic:    CRectTool(DrawShape drawShape);// Implementation    virtual void OnLButtonDown(CDrawView* pView, UINT nFlags, const CPoint& point);    virtual void OnLButtonDblClk(CDrawView* pView, UINT nFlags, const CPoint& point);    virtual void OnLButtonUp(CDrawView* pView, UINT nFlags, const CPoint& point);    virtual void OnMouseMove(CDrawView* pView, UINT nFlags, const CPoint& point);};


  限于篇幅,我不一一列举这些类的实现代码。这里简单提提视图类是如何找到绘图工具类的,它是由CDrawTool类的静态函数FindTool实现的:

 
C/C++ code
CDrawTool* CDrawTool::FindTool(DrawShape drawShape){    POSITION pos = c_tools.GetHeadPosition();    while (pos != NULL)    {        CDrawTool* pTool = (CDrawTool*)c_tools.GetNext(pos);        if (pTool->m_drawShape == drawShape)            return pTool;    }    return NULL;} 


   
  我将这个做法称之为操作面向对象化。我们看以前的C++教材,在介绍面向对象时,往往举这样的例子:如class person,就是说现实世界中的事物都可以作为对象来看待。这样的说法通俗易懂,也不能说不对。但拘泥于这样的认识,往往是将面向对象之对象仅限于此。其实不然。事物之间的联系也可以面向对象化,而且在设计复杂的系统时,这种面向对象显得更为重要。

  将操作动作面向对象化,在成熟的软件框架并不鲜见。做过ArcGis二次开发的朋友都知道,ArcGis中的SDK中有两个虚接口:ITool和ICommand。这二者其实就是将操作面向对象化。ICommand和ITool的区别在于:ICommand不需要用鼠标等与地图交互,如全图功能,ITool则需要,如选择功能。你也可以想到Drawcli中的CDrawTool和ITool的功能是一样的。

  到此时可能你会问我:你所讲的和你的题目是什么关系?下面我要说的正是这个问题的答案。Drawcli的设计正是标题中的Comand模式的一个具体体现。让我们重温一下Comand模式的应用场景:许多系统都会收到,发送并处理请求。条件调度程序是一条条语句(比如switch语句),它用来执行请求的发送和处理,有些简单情况适合它们,复杂的情况下就不适合。这种调度程序的代码如果在一页显示,还可以,但是负责情况下:
?缺少足够的运行时灵活性
?代码的膨胀

  Comand的解决方案是这种问题最好的方案。只需简单地把每块处理逻辑放到一个单独的“命令”类中,这个类有一些通用的方法,如execute(),run(),用来执行它所封装的处理逻辑。一旦有了这样一批命令类,就可以用一个集合来存储,获取它们的示例(添加,删除,修改),并通过它们的执行方法执行这些示例。

  在Drawcli例子的启发下,我这样重构我的代码。我也像ArcGis SDK那样设计,将用户的操作分为两类:需要和视图进行鼠标交互的工具类和不需要和视图进行交互的命令类。大致的代码如下:

C/C++ code
enum cmd_type{   Cmd_NoOpertin, // 无操作   Cmd_ViewAll,  // 全图显示   …};// 命令类基类class CBaseComand{    public:    CBaseComand(CMyView *pView);    virtual ~ CBaseComand();    virtual void Execute();   // 外部调用方法    //private:    CMyView *pView;  // 保存视图类指针,方便访问视图类的数据}class CViewAllCmd : public CBaseComand{    public:    CViewAllCmd(CMyView *pView);    virtual ~ CViewAllCmd ();    void Execute();   // 外部调用方法    //private:    CMyView *pView;  // 保存视图类指针,方便访问视图类的数据}


  在如何创建命令方面我并没有采用drawcli的做法(drawcli的做法是采用一个链表将各种工具保存下来)。我应用了工厂模式,即定义了一个CCmdFactory类专门用于创建命令,该类只有一个静态方法:
C/C++ code
CBaseComand* CCmdFactory::CreateCmd(CMyView *pView,cmd_type type){     switch(type)   {        case Cmd_ViewAll:            return new CViewAllCmd(pView);        ……    }}


  当需要执行某一操作时,就销毁前一个命令,然后新建一个操作命令。工具类的设计与命令类类似,不同的是工具类有响应鼠标消息的接口。所以在此不进行赘述。  

  实践证明,这次重构效果良好。这次重构的经验可以简单概括为:在重构之前我没想过要用什么设计模式,我分析代码中现存的问题及潜在的问题(重点在可维护性、可扩展性方面衡量),然后参考成熟的开源代码的做法,最后再结合所学的设计模式的理论深化自己的认识。



[解决办法]
支持下,最近一直在用工厂模式
[解决办法]
支持楼主分享经验
[解决办法]
支持楼主分享经验
[解决办法]
谢谢分享,大力支持
[解决办法]
发誓发誓非打算购房打算
[解决办法]
tinghaode
[解决办法]
支持楼主分享经验
[解决办法]
多谢!!!
[解决办法]
设计模式真的很有用也

[解决办法]
这个一定要看看~~
[解决办法]
不吸引档
[解决办法]
  不错 !!C++ 多态的应用,即可做到分发
[解决办法]
设计模式的东西,慢慢学习体会呀

[解决办法]
顶一下,最近初接触设计模式
------解决方案--------------------


过来看看。支持下。子论坛的第一帖啊!!
[解决办法]
比较实用啊,学到知识了。
[解决办法]
"在如何创建命令方面我并没有采用drawcli的做法(drawcli的做法是采用一个链表将各种工具保存下来)。我应用了工厂模式,即定义了一个CCmdFactory类专门用于创建命令,该类只有一个静态方法:"

drawcli的方法挺好,command一般都是没有状态的执行者,创建一次就好了,不用每次都new

[解决办法]
建议楼主考虑一个策略模式先。
另外设计模式一个重要的思想就是对修改封闭,对扩展开放。那如果你要增加新的command,那你的command factory是不是就要重新修改了?
[解决办法]
可以结合state模式看一下

热点排行