分类: WINDOWS
2009-04-10 11:40:36
MVC简介
MVC模式在用户交互式应用程序中有许多成功的应用,其基本思想是通过分离Model/View/Controller来构建用户界面。Model表示应用的数据,View是model在屏幕上的表示,而Controller负责处理对用户操作的反应。通过分离这三部分内容可以提高软件的灵活性和复用性,通过合理分配类的责任也有助于降低WMC和CBO指标。
Table模块现状分析
从MVC的角度分析目前的table模块,可以将现有的类先做一个粗略的划分,然后再逐步重构:
Model部分:主要是描述Table的数据结构部分:包括CTable, CCellList, CCell等
这一部分不应向外部暴露过多底层实现的细节, 而应考虑抽象出一些基本操作的接口供外部调用。总的来看,目前的实现还是不错的。不过个人以为按照页面的大小切分table, 似乎应该是放到视图层去处理。
Controller部分:负责响应用户操作(如选择菜单、拖拽点击鼠标等), 并修改model部分的数据结构。
这部分的入口函数主要存在于CTableEngine中, CTableEngine的成员变量保存了当前操作的对象。CTableProc是一个静态的工具类,提供一些公用的服务。HCUndoOP是一个全局的Undo/Redo引擎, 负责创建和存储undo对象, 并调用undo/redo方法。
每种操作都对应着一个undo类,例如拆分表格对应CUndoDivideTable, 合并单元格对应的是CUndoUnionCell等。这些类继承自CObject,且只存储了一些操作的中间结果,而没有成员函数。
这种设计使得CTableEngine规模过于庞大(WMC和CBO指标过高)。此外,HCUndoOp的API过于繁杂,没有做很好的抽象处理。同时各种UndoObject只作为数据容器,没有与相关操作很好的结合起来。
此处可以考虑把CTableEngine的功能按操作抽取出来, 并与利用现有的这些Undo类结合。 考虑应用Command模式,抽象出一个Command基类,各个具体的操作继承Command基类,并在execute()方法中描述如何进行操作。Engine中与各个操作相关的临时变量也可以分离到这些Command子类中去,带有全局性的变量 (model对象的指针保留在engine中或分离到Context类中)。
但是目前Engine定义的许多public接口无法进行修改,所以这方面还需进行更细致的考虑。
View部分:通过绘制以graphics的形式反映当前数据结构的状况。这方面的代码主要存在于CTableDraw中。以下部分重点谈一下CTableDraw的重构。
关于CTableDraw重构的考虑
分为如下几个步骤
1) 分析外部对CTableDraw的调用依赖
目前CTableDraw中所有的成员函数都是public static的,public的函数应该体现该类对外所提供的服务,而分析显示这些函数绝大部分是通过本类的其他成员函数来调用的。所以第一步应考虑甄别出该类真正的对外接口,隐藏实现的细节。
方法: 将CTableDraw这个类中所有的成员函数设置为private, 然后编译、链接。查找出编译不过的部分即为该类的对外接口,需要定义为public,而其他均可以定义为private。下表描述了最后筛选出的CTableDraw的public接口
函数名 |
外部调用 |
用途 |
static BOOL drawTable(CDC *pDC, HCCoord
&dUnit, HCFrame *pFrame, int nHeadingPos = 0); |
Drawhdoc.obj |
Table的视图是整个doc视图的一部分,在进行doc绘制时调用table绘制的方法 |
static void drawEdge(CDC *pDC, int nX, int
nY, int nLen, int nType); |
DlgCellAttr.obj |
‘单元格属性’对话框效果预览 |
static void drawTableStyleLine(CDC
*pDC, HCCoord *pUnit, int nSX, int nSY, int nEX, int nEY, CCellLine
*cellLine); |
DlgCellAttr.obj |
‘单元格属性’对话框效果预览 |
static void drawTableShape(CDC *pDC, int
nRowNum, int nColNum, int sx, int sy, int ex, int ey); |
DlgTableNew.obj |
‘新建表格’对话框效果预览 |
|
D_bSplit.obj |
‘拆分表格’效果预览 |
从中可以看出,最主要的对外接口就只有一个方法static BOOL drawTable(),应作为主要的关注点。
2) 改造静态方法为成员函数
如上所述CTableDraw的主要功能是通过绘制以graphics的形式反映当前数据结构的状况,目前是一些静态绘制方法的集合。这是一种过程式程序设计风格的体现,不是很好的OO风格,也不利于进一步的重构。但是目前又要保持静态的API不变,否则编译不过。可以考虑应用Adapter模式,把该接口改造成一个外壳, 并适配到相应的动态方法上。
例如:
static BOOL drawTable(CDC *pDC, HCCoord &dUnit, HCFrame *pFrame, int
nHeadingPos = 0)
{
pFrame -> paint(pDC, dUnit);
}
在HCFrame中增加paint()方法
void HCFrame::paint(CDC *pDC, HCCoord &dUnit)
在理想情况下,HCFrame应该能够根据内容类型的不同,绑定到不同的绘制方法上去。
目前HCFrame里装的EDS有text,image,table,cell等,它们被定义为Frame的成员变量 CObject *m_pEds; 并通过另一个成员变量BYTE m_bClass来区别具体的EDS类型。这也不是很好的OO风格,在理想的情况下,应该为HCFrame所包含的EDS抽象出一个接口来, 比如说HCEDS,然后由具体的class来实现这些接口。
但是目前只有table这种EDS的代码,所以只能这样写
static BOOL drawTable(CDC *pDC, HCCoord &dUnit, HCFrame *pFrame, int
nHeadingPos = 0)
{
if (NULL == pDC || NULL == pFrame ||
!pFrame->IsTable())
return false;
CTable *pTable = (CTable
*)pFrame->GetEDS();
pTable -> paint(pDC, dUnit);
}
3)分离model与view部分
为了避免CTable这个类承担太多的责任,降低wmc值,考虑将绘制这方面的功能由一个view类来负责。CTable将paint()方法的具体实现委托CTableView这个类来实现。而 CTableView在绘制时需要访问CTable 中的数据结构来完成绘制任务。
class CTable()
{
CTableView * m_view;
void installView()
{
m_view
= new CTableView(this);
}
void paint(CDC *pDC, HCCoord
&dUnit)
{
m_view->paint(pDC, dUnit);
}
}
class CTableView()
{
CTableView(CTable *model)
{
m_model = model;
}
void paint(CDC *pDC, HCCoord
&dUnit);
}
4) 建立composite view, 降低CBO与WMC
CTableView的paint()方法主要做两件事情, 一是绘制各单元格的内容,二是绘制表格的边框。这就涉及到访问单元格EDS, 斜线CDiagonal, 边框CCellLine等细节,
从而形成对这些类的依赖。为了降低WMC和CBO指标,可以考虑将整个表格视图拆分为内容、边框等子视图, 每个子视图由各自的类绘制具体绘制。CTableView的责任简化为遍历表格的结构,
并调用相应的绘制器来完成绘制工作。 通过合理分配类之间的责任,应该可以降低相关指标值, 调整后的结构如下图所示。