设计模式在FileBrowser中的几个应用

设计模式是代码重构的最终目标,在程序设计中有效的运用这项技术,可以大大提高代码的可读性和可维护性。使整个程序设计结构趋向精致完美。在我维护的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;

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/50034.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Sed工具

文章目录 一、sed是什么二、sed的常用操作选项三、如何使用sed1.Sed结合正则表达式输出指定行2.增加内容3.删除4.替换5.搜索替换6.插入文件7.另存为到文件8.同时编辑9.分组操作10.读取完退出11.sed脚本12.sed的高级应用 一、sed是什么 sed 命令是利用脚本来处理文本文件。它可…

[C语言]自己实现sprintf,printf函数

一、要点&#xff1a; 实现sprintf&#xff0c;printf的要点在于不定参数的接收与处理&#xff0c;即va_list的使用&#xff0c;另外使用va_list需要包含stdarg.h头函数&#xff0c;想了解详细用法可以自行查找。 另外就是涉及数据的处理与转换&#xff0c;常用的是整型转字符…

Redis的集群的搭建

1、为什么要搭建Redis集群 Redis 集群能够提供高可用性、高性能、扩展性和数据安全性&#xff0c;适用于各种需要高速缓存和数据存储的复杂应用场景 2、Redis的集群模式 主从模式哨兵模式区中心化模式 3、主从模式 redis主从模式表示一个主节点跟若干个从节点。主节点可以…

矿井水絮凝沉淀一体机处理效果高

诸城市鑫淼环保小编带大家了解一下矿井水絮凝沉淀一体机处理效果高 矿井废水的成分主要是悬浮物和可溶性无机物&#xff0c;还含少量的废机油、乳化油、废坑木腐烂物、井下粪便等有机污染物。 悬浮物SS的特点&#xff1a;含量变化范围很大&#xff0c;可以从几百到几千甚至上万…

VMware 上安装 CentOS 7 教程 (包含网络设置)

**建议先看一些我安装VMware的教程&#xff0c;有些网络配置需要做一下 1.打开VMware&#xff0c;创建虚拟机 2.勾选自定义&#xff0c;点击下一步 3.点击下一步 4.勾选“稍后安装操作系统”&#xff0c;点击下一步 5.勾选linux&#xff0c;勾选centos7&#xff0c;点击下一步…

AH1405芯片的应用领域有哪些?sot23-5封装ic

1405芯片是一种SOT23-5封装的降压转换器&#xff0c;以其出色的性能和广泛的应用领域&#xff0c;成为电子设计中的热门选择。本文将详细介绍1405芯片的技术特点以及其在不同领域的应用情况。 技术特点 1. 宽输入电压范围 1405芯片能够接受从6V至40V的输入电压&#xff0c;这…

汽车绝缘检测详细设计

粘连检测原理 粘连检测&#xff1a; 目的&#xff1a;检测继电器、开关或电气触点是否因故障而保持在接通或断开的状态。工作原理&#xff1a; 正常操作&#xff1a;继电器或开关在正常操作时会周期性地开闭。开闭过程中会有明显的电流和电压变化。粘连状态&#xff1a;如果继…

Vuex数据持久化实现

版本&#xff1a;vue 3.4.29 vuex4.1.0 1. 出现的问题 当我使用 vuex 作为状态管理组件来存储用户的一些信息之后&#xff0c;发现从/login 页面跳转到/home 界面后拿不到vuex信息。 之后查阅资料了解&#xff0c;当切换路由后&#xff0c;vue 会重新渲染&#xff0c;而vuex 也…

实战机器学习--决策树分类器在蘑菇分类中的应用

数据集&#xff1a;https://pan.quark.cn/s/4d3526600c0c 在机器学习领域&#xff0c;图像分类是一个常见的任务&#xff0c;尤其是在自然语言处理和生物识别等领域。本文将通过一个简单的例子&#xff0c;展示如何使用Python和一些流行的库来实现蘑菇的分类&#xff0c;区分可…

docker 部署 LaTeX 环境

docker 部署 LaTeX 环境 需求表述部署步骤环境补丁&#xff08;解决 picins.sty 缺失问题&#xff09; 同步发布在个人笔记docker 部署 LaTeX 环境 需求表述 想做一个项目&#xff0c;需要一个 LaTeX 的中文环境&#xff0c;由于习惯了在服务器上用 docker 部署环境&#xff0…

pgsql的update语句在set里进行字段的运算 SET sort = sort +1

一、场景 需求&#xff1a;version 版本字段是记录数据更新的次数&#xff0c;新增时自动填充 version1 ,每更新一次数据 version就自增1。项目里单表插入和更新要手写update语句进行插入和更新。 –表中int4类型的字段 version 是1时&#xff0c;由1变成2 – version 是null…

【Linux】信号(signal)

目录 一、信号概念&#xff1a; 二、信号的常见状态&#xff1a; 信号递达&#xff1a; 信号未决&#xff1a; 阻塞信号&#xff1a; 忽略信号&#xff1a; 信号在内核中的表示&#xff1a; 三、信号相关函数&#xff1a; sigset_t &#xff08;类型&#xff09;&…

二、QGroundControl开发环境搭建

文章目录 环境列表QGC源码下载编译 环境列表 QGC GithubPX4-AutopilotQt 5.15Ubuntu20.04 QGC源码下载编译 官网下载指令 如下 // Clone the repo (or your fork) including submodules: git clone --recursive -j8 https://github.com/mavlink/qgroundcontrol.git // Upda…

Axure中继器实战篇:让数据展示和交互设计更上一层楼!

Axure中继器实战篇:让数据展示和交互设计更上一层楼! 前言 经过了前两章的学习,接下来我们去模拟的实际场景开启实战篇,以下是界面 1.前期准备 前期把页面准备好后,给中继器的每个单元格命名为了方便数据绑定的操作。 为了演示我准备了几十行数据,建议也多准备一点。…

后端返回一个图片链接,前端如何实现下载功能?

纯原创文章&#xff0c;转载请说明来源。 一、背景 要实现一个下载功能&#xff0c;后端直接返回了一个图片的地址https://xxxxx/pic.jpg。如果我们直接通过window.open(url, _blank) 的方式去下载这个图片&#xff0c;会发现 Chrome 浏览器会对这个图片进行预览&#xff0c;…

魅族手机怎么录屏?详细步骤助你轻松上手

“有人知道魅族手机怎么录屏吗&#xff0c;最近我在准备一些教学视频&#xff0c;急需用到手机的录屏功能来记录操作过程&#xff0c;但遗憾的是&#xff0c;我翻遍了设置也没能找到录屏的开关。所以&#xff0c;我在这里想问问大家&#xff0c;魅族手机是如何启动录屏功能的&a…

【PyTorch】图像多分类项目部署

【PyTorch】图像多分类项目 【PyTorch】图像多分类项目部署 如果需要在独立于训练脚本的新脚本中部署模型&#xff0c;这种情况模型和权重在内存中不存在&#xff0c;因此需要构造一个模型类的对象&#xff0c;然后将存储的权重加载到模型中。 加载模型参数&#xff0c;验证模型…

Sftp和ftp 区别、工作原理

Sftp的工作原理&#xff1a; SFTP的工作原理基于SSH协议&#xff0c;通过加密连接和安全认证来保障文件传输的安全性。 SFTP&#xff08;SSH File Transfer Protocol&#xff09;是一个确保数据在传输过程中安全的协议&#xff0c;它通过为传输的数据提供加密保护和对用户…

Docker重启策略和缩小镜像体积

目录 Docker重启策略 命令格式 命令选项 Docker缩小镜像体积 Docker重启策略 命令格式 docker run --restartno|always|on-failure|unless-stopped .... 命令选项 no&#xff1a;不管容器是正常退出还是异常退出&#xff0c;都不会重启容器。默认策略。always&#xf…

Java面试锦集 之 一、Java基础(1)

一、Java基础&#xff08;1&#xff09; 1.final 关键字的作用&#xff1f; 修饰变量&#xff1a; 一旦被赋值&#xff0c;就不能再被修改&#xff0c;保证了变量值的稳定性。 例&#xff1a; final int NUMBER 10; //之后就不能再改变 NUMBER 的值了。修饰方法&#xff1a;…