抽象工厂模式实现的dll插件功能

抽象工厂模式实现的dll插件功能. 收藏

 

[本人转载于:http://blog.csdn.net/s_51563946/archive/2009/08/18/4458480.aspx]

    先谈下设计需求.一个可扩展的程序很多地方都希望是可以替换或者是可以扩展的.比如一个游戏引擎,希望渲染部分能够在D3D和OpenGL之间替换,希望shader部分能够在cg,hlsl,glsl之间切换,希望音频部分能够在DS3D和OpenAL之间切换,希望可以选择性的获得读取普通文件和读取zip或者其他输入流的能力,希望能够读取各种不同的模型文件格式的能力--而且这些都不必改变上层接口.
    现在以"选择性的获得读取普通文件和读取zip或者其他输入流的能力"为例子来看看如果利用建立dll插件的方法来实现这一功能.
    显然,我们是要构架一个文件系统.首先看看文件需要支持哪些功能:

view plaincopy to clipboardprint?
///文件输入流  
    class IFile  
    {  
    public:  
        ///打开文件.  
        virtual bool open() = 0;  
        ///关闭文件  
        virtual void close() = 0;  
        /** 
           从文件中读取指定长度的二进制内容 
           @param buffer 将文件内容读到的缓冲区 
           @param size   读出的尺寸,注意不要超过缓冲区的大小 
           @return 返回实际读出的尺寸 
        */ 
        virtual size_t read(void* buffer, size_t size) = 0;  
        /** 
           操作读指针的位置 
           @param off   指针的偏移量 
           @param stype 偏移的相对位置 
        */ 
        virtual void seekg(long off, FSE::Seek_Type stype = FSE::cur) = 0;  
        ///取得当前读指针位置  
        virtual size_t tellg() = 0;  
        ///判断文件指针是否在文件尾  
        virtual bool eof() const = 0;  
        ///取得文件尺寸  
        size_t size() const;  
        ///取得文件名  
        String file_name() const;  
    };  
 
    ///文件输出流  
    class OFile  
    {  
    public:  
        ///打开文件.  
        virtual bool open() = 0;  
        ///关闭文件  
        virtual void close() = 0;  
        /** 
           向文件中写入二进制内容 
           @param buffer 待写入的内容 
           @param size   待写入的尺寸 
           @return 实际写如的尺寸 
        */ 
        virtual size_t write(const void* buffer, size_t size) = 0;  
        /** 
           操作写指针的位置 
           @param off   指针的偏移量 
           @param stype 偏移的相对位置 
        */ 
        virtual void seekp(long off, FSE::Seek_Type stype = FSE::cur) = 0;  
        ///取得当前写指针位置  
        virtual size_t tellp() = 0;  
        ///刷新缓冲区,将写缓冲区中的内容写到文件中  
        virtual void flush() = 0;  
        ///取得文件名  
        String file_name() const;  
    }; 
///文件输入流
    class IFile
    {
    public:
        ///打开文件.
        virtual bool open() = 0;
        ///关闭文件
        virtual void close() = 0;
        /**
           从文件中读取指定长度的二进制内容
           @param buffer 将文件内容读到的缓冲区
           @param size   读出的尺寸,注意不要超过缓冲区的大小
           @return 返回实际读出的尺寸
        */
        virtual size_t read(void* buffer, size_t size) = 0;
        /**
           操作读指针的位置
           @param off   指针的偏移量
           @param stype 偏移的相对位置
        */
        virtual void seekg(long off, FSE::Seek_Type stype = FSE::cur) = 0;
        ///取得当前读指针位置
        virtual size_t tellg() = 0;
        ///判断文件指针是否在文件尾
        virtual bool eof() const = 0;
        ///取得文件尺寸
        size_t size() const;
        ///取得文件名
        String file_name() const;
    };

    ///文件输出流
    class OFile
    {
    public:
        ///打开文件.
        virtual bool open() = 0;
        ///关闭文件
        virtual void close() = 0;
        /**
           向文件中写入二进制内容
           @param buffer 待写入的内容
           @param size   待写入的尺寸
           @return 实际写如的尺寸
        */
        virtual size_t write(const void* buffer, size_t size) = 0;
        /**
           操作写指针的位置
           @param off   指针的偏移量
           @param stype 偏移的相对位置
        */
        virtual void seekp(long off, FSE::Seek_Type stype = FSE::cur) = 0;
        ///取得当前写指针位置
        virtual size_t tellp() = 0;
        ///刷新缓冲区,将写缓冲区中的内容写到文件中
        virtual void flush() = 0;
        ///取得文件名
        String file_name() const;
    };

   这是个简单的继承设计.比如我们要实现普通文件和zip文件的操作功能,我们只需要:
    class Normal_IFile : public IFile
    class Normal_OFile : public OFile
    class Zip_IFile : public IFile
    class Zip_OFile : public OFile
    这样继承,然后分别实现其中的操作就好了.
    但是这里需要注意到的是,zip文件并不只是单单包含一个压缩文件.一个zip文件可能是一个文档目录的压缩.因此我们还需要增加类:
view plaincopy to clipboardprint?
///文件目录类,代表一个文件目录  
class Archive  
{  
public:  
    /** 
       取得此文档的类型. 
       @return: 
           如果返回为空串,表明为普通文档.其他 
           返回值由具体使用的文件系统插件决定. 
           例如使用PgeZipFileSys插件时,如果打 
           开的此文档是zip文档,则返回为"zip". 
    */ 
    virtual String type() = 0;  
    /** 
       在此目录下打开指定文件的输入流. 
       @param fileName 文件的相对路径与文件名 
       @return 返回文件指针,如果为NULL代表打开失败 
    */ 
    virtual IFile_SPtr open_input_file(const String& file_name) = 0;  
    /** 
       在此目录下打开指定文件的输出流. 
       @param fileName 文件的相对路径与文件名 
       @return 返回文件指针,如果为NULL代表打开失败 
    */ 
    virtual OFile_SPtr open_output_file(const String& file_name) = 0;  
    ///取得目录名称  
    String archive_name();  
}; 
    ///文件目录类,代表一个文件目录
    class Archive
    {
    public:
        /**
           取得此文档的类型.
           @return:
               如果返回为空串,表明为普通文档.其他
               返回值由具体使用的文件系统插件决定.
               例如使用PgeZipFileSys插件时,如果打
               开的此文档是zip文档,则返回为"zip".
        */
        virtual String type() = 0;
        /**
           在此目录下打开指定文件的输入流.
           @param fileName 文件的相对路径与文件名
           @return 返回文件指针,如果为NULL代表打开失败
        */
        virtual IFile_SPtr open_input_file(const String& file_name) = 0;
        /**
           在此目录下打开指定文件的输出流.
           @param fileName 文件的相对路径与文件名
           @return 返回文件指针,如果为NULL代表打开失败
        */
        virtual OFile_SPtr open_output_file(const String& file_name) = 0;
        ///取得目录名称
        String archive_name();
    };
 

   然后仍然按照继承的方法,
    class Normal_Archive : public Archive
    class Zip_Archive : public Archive

 这样我们就有了一组相关的子类.这种情况正是使用抽象工厂模式的时机:

view plaincopy to clipboardprint?
///文档工厂,产生文档  
class Archive_Facroty  
{  
public:  
    /** 
       打开一个目录. 
       @param  dirname 待打开的目录路径 
       @param  type    文档的类型 
       @return 返回此目录的Archive对象指针 
    */ 
    virtual Archive_SPtr open_archive(const String& dirname) = 0;  
    /** 
       返回该文档工厂的类型.注意务必保持和文档的类型名一致. 
    */ 
    virtual String type() = 0;  
}; 
    ///文档工厂,产生文档
    class Archive_Facroty
    {
    public:
        /**
           打开一个目录.
           @param  dirname 待打开的目录路径
           @param  type    文档的类型
           @return 返回此目录的Archive对象指针
        */
        virtual Archive_SPtr open_archive(const String& dirname) = 0;
        /**
           返回该文档工厂的类型.注意务必保持和文档的类型名一致.
        */
        virtual String type() = 0;
    };
 
然后再进行一次继承,分别产生普通目录的工厂和zip目录的工厂
    class Normal_Archive_Facroty : public Archive_Facroty
    class Zip_Archive_Facroty : public Archive_Facroty
这样设置后,就可以把不同类型(普通 or zip)的文档的创建责任放到一个对象上.这样就方便了我们下一个类的设计:File_System
view plaincopy to clipboardprint?
文件系统的抽象层  
class File_System  
{  
    /** 
       装载插件. 
    */ 
    void load_plugin(const String& plugin);  
 
    /** 
       添加搜索文档. 
       @param spath 搜索文档的名称 
       @param type  文档的类型,默认为空是指普通字符串. 
                    其他类型由具体加载插件指定.例如zip 
                    文件系统插件支持"zip"格式. 
    */ 
    void add_search_archive(const String& spath, const String& type = "");  
 
    /** 
       打开指定文档.如果已存在,则返回已存在的指针,否则 
       建立新的文档类.保证不会重复打开文件. 
    */ 
    Archive_SPtr open_archive(const String& arcname, const String& type = "");  
 
    /** 
       打开一个输入文件. 
       @note: 
           此函数首先会在已注册的搜索文档中按添加路径的顺 
           序对文档中的文件进行搜索,直到找到第一个文件为止. 
       @return 
           如果打开失败,将返回空指针 
    */ 
    IFile_SPtr open_input_file(const String& filename);  
private:  
    //文档工厂列表  
    typedef std::list<Archive_Facroty*> Archive_Factory_List;  
    Archive_Factory_List m_arch_facty_list;  
    //文档类的观察指针  
    typedef boost::weak_ptr<Archive> Archive_VPtr;  
    //保存zip文档的映射表.键:文档路径名,值:文档类的观察指针  
    typedef std::map<String, Archive_VPtr> ArchiveMap;  
    ArchiveMap archives;  
    //搜索路径文档  
    typedef std::list<Archive_SPtr> Search_Archive_List;  
    Search_Archive_List m_search_archives;  
}; 
    文件系统的抽象层
    class File_System
    {
        /**
           装载插件.
        */
        void load_plugin(const String& plugin);

        /**
           添加搜索文档.
           @param spath 搜索文档的名称
           @param type  文档的类型,默认为空是指普通字符串.
                        其他类型由具体加载插件指定.例如zip
                        文件系统插件支持"zip"格式.
        */
        void add_search_archive(const String& spath, const String& type = "");

        /**
           打开指定文档.如果已存在,则返回已存在的指针,否则
           建立新的文档类.保证不会重复打开文件.
        */
        Archive_SPtr open_archive(const String& arcname, const String& type = "");

        /**
           打开一个输入文件.
           @note:
               此函数首先会在已注册的搜索文档中按添加路径的顺
               序对文档中的文件进行搜索,直到找到第一个文件为止.
           @return
               如果打开失败,将返回空指针
        */
        IFile_SPtr open_input_file(const String& filename);
    private:
        //文档工厂列表
        typedef std::list<Archive_Facroty*> Archive_Factory_List;
        Archive_Factory_List m_arch_facty_list;
        //文档类的观察指针
        typedef boost::weak_ptr<Archive> Archive_VPtr;
        //保存zip文档的映射表.键:文档路径名,值:文档类的观察指针
        typedef std::map<String, Archive_VPtr> ArchiveMap;
        ArchiveMap archives;
        //搜索路径文档
        typedef std::list<Archive_SPtr> Search_Archive_List;
        Search_Archive_List m_search_archives;
    };
 

    File_System有两个功能,一是通过load_plugin函数来装载不同的文档工厂,从而达到读写不同文件格式的能力.二是增加了搜索路径功能,可以方便的通过默认设置的搜索路径来打开文件.当然第二个功能在这里和整个设计结构关系不大.
    这里要注意的是File_System中的文档工厂链表:
    typedef std::list<Archive_Facroty*> Archive_Factory_List;
    Archive_Factory_List m_arch_facty_list;
    每次调用load_plugin,都会想这个链表中添加一个新项.这里属于一个责任链的设计模式,当使用open_archive函数的时候,文件系统会在Archive_Factory_List中查找类型匹配的文档工厂,让其负责该文档的产生.
//-----------------------------------------------------------------------------------
    说到这里,整个系统就分成了3个工程.第一个主工程,其中包括了类:
    File_System, Archive, Archive_Facroty, IFile和OFile.
    第二个是普通文件的插件,这里假设工程将产生一个名为plugin_file_system_normal.dll的动态库,其中的类包括:
    Normal_Archive, Normal_Archive_Facroty, Normal_IFile和Normal_OFile.
    第三个是zip文件的插件,假设工程将产生一个名为plugin_file_system_zip.dll的动态库,其中的类包括:
    Zip_Archive,Zip_Archive_Facroty,Zip_IFile和Zip_OFile.
//-----------------------------------------------------------------------------------

   好了,到目前这个设计基本完成了,只剩下动态库插件跟主工程之间的粘连工作了.根据上面代码片段的注释,我们应该知道File_System::load_plugin函数的参数是dll的名称.那么这个函数内部所做的工作实际就是打开一个dll动态库,创建文档工厂对象.
    为了能利用动态库创建文档工厂,我们需要为动态库添加一个导出函数.动态库的导出书写方式根据平台不同而不同.一般如果是windows平台上的动态库,导出函数前要加修饰前缀:__declspec( dllexport ).因此为了实现跨平台,通过宏定义隔离这些差别:
view plaincopy to clipboardprint?
//导出定义  
//windows平台下  
#if PLATFORM == PLATFORM_WIN32  
    #define Nor_FS_Export __declspec( dllexport )  
//linux平台下  
#else  
    #define Nor_FS_Export  
#endif  
//导出函数  
extern "C" Nor_FS_Export Archive_Facroty* create_plugin()  
{  
    return new Normal_Archive_Facroty();  

    //导出定义
    //windows平台下
    #if PLATFORM == PLATFORM_WIN32
        #define Nor_FS_Export __declspec( dllexport )
    //linux平台下
    #else
        #define Nor_FS_Export
    #endif
    //导出函数
    extern "C" Nor_FS_Export Archive_Facroty* create_plugin()
    {
        return new Normal_Archive_Facroty();
    }
 

   在主程序一方也需要动态的打开动态库.而这些操作函数也是因操作系统不同而不同的.因此在这里也对其进行了封装.封装后接口如下(实现就不写了):
view plaincopy to clipboardprint?
class Dynamic_Lib  
{  
public:  
    ///装载动态库  
    void load(const String& libname);  
    ///卸载动态库  
    void unload();  
    ///取得动态函数  
    void* get_process(const String& procname) const;  
 }; 
    class Dynamic_Lib
    {
    public:
        ///装载动态库
        void load(const String& libname);
        ///卸载动态库
        void unload();
        ///取得动态函数
        void* get_process(const String& procname) const;
     };
 

以及对动态库进行管理的类:
view plaincopy to clipboardprint?
class Dynamic_Lib_Mgr  
{  
public:  
    /** 
        如果库还未装载,则装载此动态连接库, 
        否则返回此动态连接库的对象指针. 
    */ 
    Dynamic_Lib* load(const String& libname);  
    ///根据库名卸载动态连接库  
    void unload(const String& libname);  
}; 
 class Dynamic_Lib_Mgr
 {
 public:
     /**
         如果库还未装载,则装载此动态连接库,
         否则返回此动态连接库的对象指针.
     */
     Dynamic_Lib* load(const String& libname);
     ///根据库名卸载动态连接库
     void unload(const String& libname);
 };

这个管理类主要是为了防止动态库被重复加载.

现在我们可以看下File_System::load_plugin的具体实现代码了:
view plaincopy to clipboardprint?
void File_System::load_plugin(const String& plugin)  
 {  
     LOG_MESSAGE("File system load a plugin", plugin, Log::LL_LOW);  
     Dynamic_Lib* lib = Dynamic_Lib_Mgr::instance().load(plugin);  
     typedef Archive_Facroty* (*lp_create_archive_factory)();  
     lp_create_archive_factory caf = (lp_create_archive_factory)lib->get_process("create_plugin");  
     m_arch_facty_list.push_back(caf());  
 } 
   void File_System::load_plugin(const String& plugin)
    {
        LOG_MESSAGE("File system load a plugin", plugin, Log::LL_LOW);
        Dynamic_Lib* lib = Dynamic_Lib_Mgr::instance().load(plugin);
        typedef Archive_Facroty* (*lp_create_archive_factory)();
        lp_create_archive_factory caf = (lp_create_archive_factory)lib->get_process("create_plugin");
        m_arch_facty_list.push_back(caf());
    }
 

    步骤分别是:写下装载插件的日志记录->打开动态库->创建create_plugin函数指针->从dll中取得该函数地址->使用该函数创建工厂对象并加到工厂链表中.
    步骤分别是:写下装载插件的日志记录->打开动态库->创建create_plugin函数指针->从dll中取得该函数地址->使用该函数创建工厂对象并加到工厂链表中.
//-----------------------------------------------------------------------------------
现在要使用这个文件系统很简单:
File_System filesys;
filesys.load_plugin("plugin_file_system_normal.dll");//让文件系统获得普通文件的读写能力
filesys.load_plugin("plugin_file_system_zip.dll");//让文件系统获得Zip文件的读写能力
//添加搜索路径
filesys.add_search_archive("test/");
m_filesys.add_search_archive("test.zip", "zip");
IFile_SPtr file1 = m_filesys.open_input_file("1234.txt");//1234.txt在普通目录test/下
IFile_SPtr file2 = m_filesys.open_input_file("ReadMe.txt");//ReadMe.txt在test.zip里面
......
//-----------------------------------------------------------------------------------
这样就实现了通过插件选择指定能力的功能

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/s_51563946/archive/2009/08/18/4458480.aspx

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值