设计模式是代码重构的最终目标,在程序设计中有效的运用这项技术,可以大大提高代码的可读性和可维护性。使整个程序设计结构趋向精致完美。在我维护的FileBrowser模块中可以针对以下方面
应用相应的模式。
1. 使用策略模式来处理文件夹扫描操作
作为网络文件浏览器,FileBrowser中自然有很多对文件夹的操作。包括计算文件夹信息,删除文件夹等,这些操作采用的都是标准的文件夹遍历的代码,只是对于各文件/文件夹进行不同的处理而已,基本代码如下:
HANDLE fp = FindFirstFile(szLocalFName, &fd);
while(TRUE)
{
if(fd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
{
Handle the folder…
}
Else
{
Handle the file…
}
if(!FindNextFile(fp, &Fd))
break;
}
显而易见,这里很适合使用策略模式:定义一个通用的扫描函数,定义策略类架构,使每个策略对象对应一个文件处理方法。这样可以避免模块中大量的相似的文件夹遍历的代码,使之简洁明了,而且更加容易扩充。
我们看一下策略模式的定义:
结构图:
基于这个思想,我设计了以下文件夹扫描结构:
class folder_handler
{
public:
folder_handler(const tstring& root) : m_root(root){}
virtual ~folder_handler(){}
virtual bool handle_folder
(const tstring&, const WIN32_FIND_DATA&){ return true; }
virtual bool handle_file(const tstring&, const WIN32_FIND_DATA&)
{ return true; }
virtual bool folder_first(){ return true; }
protected:
tstring m_root;
};
void scan_folder(const tstring& folder_path, folder_handler& h)
{
tstring child_path(_T(""));
string find_name = folder_path + _T(“//*.*”);
WIN32_FIND_DATA fd = {0};
HANDLE hFindFile = FindFirstFile(find_name.c_str(), &fd);
if (INVALID_HANDLE_VALUE == hFindFile)
{
cout << "Cann't access folder " << folder_path << endl;
return;
}
// 先处理父文件夹
if (h.folder_first())
{
h.handle_folder(folder_path);
}
do
{
if (_T('.') == fd.cFileName[0])
{
continue;
}
child_path = folder_path + _T("//") + fd.cFileName;
if (FILE_ATTRIBUTE_DIRECTORY & fd.dwFileAttributes)
{
scan_folder(child_path, h);
}
else
{
m_numfiles++;
m_foldersize+=
MAKELONGLONG(fd.nFileSizeLow, fd.nFileSizeHigh);
h.handle_file(child_path, fd);
}
} while(FindNextFile(hFindFile, &fd));
FindClose(hFindFile);
// 后处理父文件夹
if (!h.folder_first())
{
h.handle_folder(folder_path);
}
}
这样我们想对文件夹做某种特定操作的时候,只需要定义一个新类,指定对子文件/子文件夹的特定操作即可,而不需要关注其遍历过程的细节,耦合度非常的低。例如我们想删除文件夹和扫描文件夹同步信息的话那么就可以定义以下两个类。
class folder_deleter : public folder_handler
{
public:
folder_deleter(const tstring& parent_folder)
: folder_handler(parent_folder)
{
}
virtual bool handle_folder
(const tstring& folder_path, const WIN32_FIND_DATA&)
{
if (!::RemoveDirectory(folder_path.c_str()))
{
_tcout << _T("Fail to remove the folder ") << folder_path << endl;
_tcout << _T("The error code ") << ::GetLastError() << endl;
return false;
}
return true;
}
virtual bool handle_file
(const tstring& file_path, const WIN32_FIND_DATA&)
{
if (!::DeleteFile(file_path.c_str()))
{
_tcout << _T("Fail to delete the file ") << file_path << endl;
_tcout << _T("The error code ") << ::GetLastError() << endl;
return false;
}
return true;
}
virtual bool folder_first(){ return false; }
};
class folder_inforecorder : public folder_handler
{
typedef struct
{
DWORD attrib;
LONGLONG size;
LONGLONG modified_time;
tstring path;
}file_info;
typedef vector<file_info> file_infos;
typedef file_infos::iterator fis_iter;
typedef file_infos::const_iterator fis_citer;
public:
folder_inforecorder(const tstring& parent_folder)
: folder_handler(parent_folder) {}
~folder_inforecorder(){}
virtual bool handle_file
(const tstring& folder_name, const WIN32_FIND_DATA& fd)
{
file_info fi;
fi.attrib = fd.dwFileAttributes;
fi.size = MAKELONGLONG(fd.nFileSizeLow, fd.nFileSizeHigh);
fi.modified_time =
MAKELONGLONG(fd.ftLastWriteTime.dwLowDateTime,
fd.ftLastWriteTime.dwHighDateTime);
tstring file_path = folder_name + _T("//") + fd.cFileName;
const tstring parentfolder = m_root + _T("//");
tstring::size_type pos = file_path.find(parentfolder);
if (tstring::npos == pos)
fi.path = file_path.c_str();
else
fi.path = file_path.substr(pos + parentfolder.size(),
tstring::npos).c_str();
m_FileInfos.push_back(fi);
return true;
}
public:
file_infos m_FileInfos;
};
使用的时候调用如下代码即可:
scanfolder scan
// 删除路径
scan.run_scan(folder_path, folder_deleter());
folder_inforecorder fir;
// 扫描路径文件信息
scan.run_scan(folder_path, fir);
2. 使用Command命令模式实现文件操作的Undo/Redo
FileBrowser对于文件操作的目前不支持Undo/Redo功能,用户所有的操作都是不可逆的,所以对于用户来说存在一定的操作风险以及不便。我们可以利用Command命令模式来实现这个功能:
将用户的命令封装成不同的对象,支持统一的接口Execute /UnExecute;定义一个命令管理类,支持Undo/Redo接口,维护一个执行命令对象链;每当用户完成一个操作之后,便生成相应的命令对象,将其加入命令对象链尾部,并将当前命令对象迭代器指向它。执行Undo/Redo功能时,根据当前命令对象迭代器调用当前命令对象Execute /UnExecute接口,并将当前命令对象迭代器在命令对象链中做相应的移动。
命令对象链操作示意图
当前命令对象迭代器 |
命令对象链 |
Figrure 3-1
命令对象类体系图
大致代码如下:
class CCommand
{
public:
CCommand(){}
virtual ~CCommand(){}
virtual void Execute() = 0;
virtual void UnExecute() = 0;
};
class CCommandManager
{
public:
CCommandManager() : m_CurPos(m_cCommandList.end()){}
virtual ~CCommandManager(){ Clear(); }
void AddCommand(CCommand* pcCommand)
{
ASSERT(pcCommand);
m_cCommandList.erase(m_cCommandList.begin(), m_CurPos);
m_cCommandList.insert(m_CurPos, pcCommand);
m_CurPos--;
}
BOOL CanUndo()
{
return (m_cCommandList.size() > 0 &&
m_CurPos != m_cCommandList.end());
}
BOOL CanRedo();
{
return (m_cCommandList.size() > 0
&& m_CurPos != m_cCommandList.begin());
}
CCommand* Undo()
{
CCommand* pcCommand = NULL;
if (CanUndo())
{
pcCommand = *m_CurPos;
ASSERT(pcCommand);
pcCommand->UnExecute();
m_CurPos++;
}
return pcCommand;
}
CCommand* Redo()
{
CCommand* pcCommand = NULL;
if (CanRedo())
{
m_CurPos--;
pcCommand = *m_CurPos;
ASSERT(pcCommand);
pcCommand->Execute();
}
return pcCommand;
}
void Clear();
private:
typedef list<CCommand*> commands;
typedef commands::iterator commands_iter;
commands m_cCommandList;// 命令对象指针容器
commands_iter m_CurPos;// 当前命令对象迭代器
};
比如我们想支持文件名命名操作Undo/Redo的话,定义以下类即可
class CCommand_Rename
{
public:
CCommand_Rename (){}
virtual ~CCommand_Rename (){}
virtual void Execute()
{
_trename(m_newfilename, m_oldfilename)
}
virtual void UnExecute()
{
_trename(m_oldfilename, m_newfilename);
}
private:
tstring m_oldfilename, m_newfilename;
};
3. 使用智能指针来管理内存(代理模式).
在程序中维护大量的指针,是非常头疼的问题。我们经常会忘记释放某些已分配内存的指针,特别是在一些复杂逻辑和很多出口的函数中,从而造成一些内存漏洞。而且有的地方我们为了保证释放内存,不得不写很多冗余的代码或者借助于一些不良的语法。
我们来看如下逻辑:
1) 先对指针A分配内存,A分配内存如果成功,那么对指针B分配内存,如果失败返回.
2) 如果B分配失败那么释放指针A返回,如果成功。那么对指针C分配内存.
3) 如果C分配失败那么释放指针A,B返回。如果成功对指针D分配内存.
4) 如果D分配失败那么释放指针A,B,C返回
…
一般的代码可能是这样
CA* A = new CA;
If (!A) return;
A->f1()
CB* B = new CB;
If (!B)
{
delete A;
return 0;
}
B-> f2();
CC* C = new CC;
If (!C)
{
delete A;
delete B;
return 0;
}
C-> f3();
CD* D = new CD;
If (!D)
{
delete A;
delete B;
delete C;
return 0;
}
…
这样的代码看上去非常的罗嗦,增加了代码阅读的困难,而且很容易遗漏一些内存释放的语句。
而有的程序员为了避免啰嗦,不得不借助于“goto”语句。
CA* A = NULL;
CB* B = NULL;
CC* C = NULL;
CD* D = NULL;
A = new CA;
if (!A) return 0;
A-> f1();
B = new CB;
if (!B)
{
goto CLEANUP;
}
B-> f2();
C = new CC;
if (!C)
{
goto CLEANUP;
}
C-> f3();
D = new CD;
if (!D)
{
goto CLEANUP;
}
…
CLEANUP:
if(A) delete A;
if(B) delete B;
if(C) delete C;
return 0;
但是goto语句早就已经是臭名昭著,其危害性很多文章里都有详细的描述,因此应该尽量避免,我们这里可以利用智能指针来避免这种尴尬。
auto_ptr<CA> A(new CA);
if (!&A) return 0;
A->f1();
auto_ptr<CB> B(new CB);
if (!&B) return 0;
B->f2();
auto_ptr<CC> C(new CC);
if (!&C) return 0;
C->f3();
auto_ptr<CD> D(new CD);
if (!&D) return 0;
…
这样代码简洁得多,而且由于它是在函数退出时,弹栈调用析构函数对所代理的指针进行删除,所以不会存在任何内存漏洞。
单件模式结构图:
4. 使用单件模式来管理资源
作为一个GUI程序,FileBrowser自然会和很多资源打交道,
目前这部分逻辑实现的不是很理想。有的地方通过全局变量有的是通过全局函数来访问资源.感觉上程序风格不是很统一,我们应该尽量避免使用全局变量。另外调用全局函数则每次都要加载释放资源动态库,比较的冗余低效。因为资源这块对于模块来讲只有唯一一个实例,而且可以被整个系统调用。所以可以使用单件模式来管理它。
定义一个资源管理管理类,保证其只有一个实例,模块中所有资源调用都是通过调用它的接口,结构图如下:
大致代码如下:
class CResManager
{
protected:
CResManager (){ LoadResouce(); }
~ CResManager (){FreeResouce (); }
public:
static CResManager& Instance()
{
static CResManager instance;
return instance;
}
void FsLoadString(UINT idString, LPTSTR lptsBuffer, DWORD dwLen);
void FsLoadBitMap(…);
void FsLoadMenu(HMENU* phMenu, UINT idMenu);
…
private:
void LoadResouce(){…}
void FreeResouce(){…}
private:
HINSTANCE m_hRes;
}
这样用户想读取一个字符串的话,调用以下语句即可
CResManager::Instance().FsLoadString(…);
整个模块的资源调用就显得统一简洁高效。
5. 使用备忘录模式来实现本地/远程目录的“前进/后退”功能
Windows自带的浏览器支持前进/后退的功能,用户可以来回浏览刚才访问过的路径,而不需要记住并敲入路径名,很是方便。其实利用备忘录模式,可以很容易的在FileBrowser中加入这一功能。
备忘录模式的机制在于定义一个对象状态的结构,当对对象进行操作之前,保存对象状态,撤销操作时根据保存的对象状态恢复此对象。这个描述看上去很像命令模式,不过命令模式保存的是对对象的操作而不是对象状态。
由于我们只要保存的文件夹路径这一单一信息,所以直接可以设置一个字符串链表即可,每次用户浏览新的路径,将当前路径名字符串保存到路径链表,并设置当前路径迭代器指向此字符串。用户前进/后退时,设置浏览器当前路径为路径迭代器所指向路径,并对路径迭代器做相应的调整。
路径链表操作示意图:
当前路径迭代器 |
路径字符串链表 |
大致代码如下:
class CPathControl
{
public:
CPathControl(void) : m_bInitialize(false) {}
~CPathControl(void){}
void CPathControl::Initialize(const tstring& path)
{
ASSERT(m_cPathList.empty());
m_cPathList.push_back(path);
m_CurPos = m_ PathList.begin();
m_bInitialize = true;
}
void AddNewPath(const tstring& path)
{
ASSERT(m_bInitialize);
m_CurPos = m_ cPathList.insert(++m_CurPos, path);
}
void GetCurPath(tstring& path)
{
ASSERT(m_bInitialize);
if (m_cPathList.size() > 0 && m_CurPos != m_cPathList.end())
(*m_CurPos).swap(path);.
}
BOOL CanMoveLast(void)
{
return (m_cPathList.size() > 0 && m_CurPos != m_cPathList.begin());
}
BOOL CanMoveNext(void)
{
list<st_viewinfo>::iterator iter = m_CurPos;
return (m_cPathList.size() > 0 && ++iter != m_cPathList.end());
}
void LastView(void)
{
if (!CanMoveLast())
m_CurPos--;
}
void NextView(void)
{
if (CanMoveNext())
m_CurPos++;
}
public:
list<tstring> m_cPathList;
list<tstring>::iterator m_CurPos;
private:
bool m_bInitialize;
};
class CFsSpDoc
{
…
Public:
void SetNewPath(const tstring& path)
{
m_cPathControl. AddNewPath(path);
BrowsePath(path);//浏览该路径
}
void Forward()
{
m_cPathControl.NextView();
tstring path(_T(“”));
m_cPathControl.GetPath(path);
BrowsePath(path); //浏览该路径
}
void Backward()
{
m_cPathControl.LastView();
tstring path(_T(“”));
m_cPathControl.GetPath(path);
BrowsePath(path);
}
…
private:
CPathControl m_cPathControl;
}