上篇《大话设计模式C++版——抽象工厂模式》中,我们拯救世界未遂,留下小小的遗憾,本篇中我们将给出一个解决方案——COM组件技术,同时也顺便扯扯工厂模式在COM组件技术中的应用。

工厂模式违背开放—封闭原则的根本原因在于对象的产生无法通过客户模块外的数据进行控制,如果我们能从xml、注册表、配置文件中写入一个类的名字,然后模块从中读出类名,并根据读出的类名创建对象,那不就和C#高大上的反射技术一样牛B哄哄了。非常幸运,微软的COM组件技术就提供了这么一个平台。

1、COM组件是神马

为了节约篇幅,这个请自行百度

2、COM组件的实现  

为了揭开COM组件的本质,我们从0开始打造一个进程内COM组件,不使用ATL已自动化的一套。

2.1 COM组件的基础知识(已了解的自行跳过)
2.1.1 COM组件中的返回类型
    HRESULT是COM组件中专用的返回类型, HRESULT 实际是个 long 类型,大于或等于0时表示成功,为负值时表示失败且自身是一个失败码,是不是略感蛋疼,微软大神没事又弄个奇葩的返回值玩玩。
2.1.2 GUID
    GUID 和 IID 实际都是一个货,是一个128位的数字,号称全球唯一识别码,VS提供工具生成,保证每一个独一无二,COM组件就是用这个来代替类名,防止有同名冲突。
2.1.3 IUnknown接口
    IUnkown接口是COM组件世界的源头,所有的组件接口和工厂类都必须继承它,why are you so diao?

class	IUnknown
{
public:
virtual ULONG AddRef() = 0;
virtual ULONG Release() = 0;
virtual HRESULT QueryInterface(REFIID riid, void **ppvObject) = 0;
};

IUnkown接口中 AddRef() 和 Release()是用来增加或减少接口对象的引用,当引用计数为0时,就删除对象自身,所以使用完COM接口后要记得 Release(),这样就会自动释放了,而不需要delete。QueryInterface()函数是返回对象接口的函数,参数 riid 是对象接口的IID,可以理解为请求的类名,参数 ppvObject 则为返回接口对象指针的指针。
2.1.4 IClassFactory
    IClassFactory接口是所有工厂类的祖宗,所有工厂类都要继承它,当然它还是必须继承更吊的IUnknown接口的。

class	IClassFactory : public IUnknown
{
public:
virtual HRESULT CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv) = 0;
virtual HRESULT LockServer(BOOL bLock) = 0;
};

IClassFactory 接口中最重要的是 CreateInstance() 函数,由它返回最终的用户使用的COM对象,第一个参数 pUnkOuter 表示是否聚合,我们这里都为NULL,表示不聚合,riid 是返回用户的COM对象的IID,ppv为返回用户的COM对象指针的指针。

2.2 新建一个Dll工程,导出如下4个函数

STDAPI DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv )
STDAPI DllCanUnloadNow()
STDAPI DllRegisterServer()
STDAPI DllUnregisterServer()

最关键的是DllGetClassObject函数,其用来返回工厂对象,如何实现稍后再讲。

2.3 仍以上篇《大话设计模式C++版——抽象工厂模式》的数据库操作为例,改用COM组件技术实现
2.3.1 定义抽象接口(注意要继承 IUnknown)

class IEmployee : public IUnknown
{
public:
virtual bool InserttoDB(Employee& stEmployee) = 0;
virtual Employee GetEmployee(int nID) = 0;
}; class IDepartment : public IUnknown
{
public:
virtual bool InserttoDB(Department& stDepartment) = 0;
virtual Department GetDepartment(int nID) = 0;
};

2.3.2 COM对象接口实现,CDataBasefromMysql实现类似(新增 IUnknown 接口的实现)

class	CDataBasefromAccess : public IEmployee, public IDepartment
{
public:
CDataBasefromAccess() : m_ulRefCount(0) {} ULONG STDMETHODCALLTYPE AddRef( void)
{
return ++m_ulRefCount;
} ULONG STDMETHODCALLTYPE Release( void)
{
ULONG ulRet = --m_ulRefCount; //如果对象引用计数为0,则删除自身
if ( 0 == ulRet )
{
delete this;
}
return ulRet;
} HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
{
HRESULT hr = S_OK; //如果请求的是默认接口或者IID_IEmployee接口都予以返回IEmployee接口
if (IsEqualIID(riid, IID_IEmployee))
{
*ppvObject = (IEmployee*)this;
}
else if (IsEqualIID(riid, IID_IDepartment))
{
*ppvObject = (IDepartment*)this;
}
else
{
hr = E_NOINTERFACE;
} if (SUCCEEDED(hr))
{
AddRef();
} return hr;
} //IEmployee接口实现函数
bool InserttoDB(Employee& stEmployee)
{
_tprintf(_T("Insert employee %s into access\n"), stEmployee.tstrName.c_str());
return true;
} Employee GetEmployee(int nID)
{
Employee stEmployee;
printf("Get an employee from access by id %d\n", nID);
return stEmployee;
} //IDepartment接口实现函数
bool InserttoDB(Department& stDepartment)
{
_tprintf(_T("Insert Department %s into access\n"), stDepartment.tstrDepartmentName.c_str());
return true;
} Department GetDepartment(int nID)
{
Department stDepartment;
printf("Get an Department from access by id %d\n", nID);
return stDepartment;
}
private:
ULONG m_ulRefCount;
};

由于COM组件技术中要求从QueryInterface()中返回1个或多个COM接口对象,并返回的任意一个接口能够查询到其他返回COM接口对象,故新增一个类继承并实现 IEmployee 和 IDepartment 接口,然后在QueryInterface()中通过类型强制转换的方式返回riid对应的COM接口对象,if else if 的结构返回接口对象的方式有点像简单工厂模式,但手法不一样了。
2.3.3 对象工厂实现,CFactoryfromAccess实现类似

class CFactoryfromMysql : public IClassFactory
{
public:
CFactoryfromMysql(void) : m_ulRefCount(0) {} ULONG STDMETHODCALLTYPE AddRef( void)
{
return ++m_ulRefCount;
} ULONG STDMETHODCALLTYPE Release( void)
{
ULONG ulRet = m_ulRefCount--; //如果对象引用计数为0,则删除自身
if ( 0 == ulRet )
{
delete this;
}
return ulRet;
} HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
{
HRESULT hr = S_OK;
//如果请求的是默认接口或者IID_IEmployee接口都予以返回接口指针,并增加引用
if (IsEqualIID(riid, IID_IClassFactory) || IsEqualIID(riid, IID_IFactoryfromMysql))
{
*ppvObject = (IClassFactory*)this;
}
else if (IsEqualIID(riid, IID_IUnknown))
{
*ppvObject = (IUnknown*)this;
}
else
{
hr = E_NOINTERFACE;
} if (SUCCEEDED(hr))
{
AddRef();
}
return hr;
} HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv)
{
if (pUnkOuter)
{
return CLASS_E_NOAGGREGATION; //不支持聚合
} CDataBasefromMysql* poCDataBasefromMysql = new CDataBasefromMysql(); if (NULL == poCDataBasefromMysql)
{
return E_OUTOFMEMORY;
} HRESULT hr = poCDataBasefromMysql->QueryInterface(riid, ppv);
if (FAILED(hr))
{
delete poCDataBasefromMysql;
return hr;
} return S_OK;
} HRESULT STDMETHODCALLTYPE LockServer(BOOL bLock)
{
bLock ? g_ulDllRefCount++ : g_ulDllRefCount--; return S_OK;
} private:
ULONG m_ulRefCount;
};

COM组件技术要求一个COM对象需要一个对应的工厂生产,即要满足工厂方法模式,其生产对象是在CreateInstance()中 new 出一个生产对象后用riid去请求对象的QueryInterface函数最终返回用户需要的COM对象。那么工厂对象又是从那来的呢?
2.3.4 DllGetClassObject
    DllGetClassObject是4个导出函数之一,其作用就是根据riid返回工厂对象,注意,此处的riid是工厂类的riid,并不是COM接口对象的riid,rclsid是COM组件的GUID,用以区别其他COM组件模块,用户请求COM接口对象时需指定。

STDAPI DllGetClassObject ( REFCLSID rclsid, REFIID riid, void** ppv )
{
if(!IsEqualGUID(rclsid, CLSID_IDataBase))
{
return CLASS_E_CLASSNOTAVAILABLE;
} HRESULT hr = S_OK;
IClassFactory* poCClassFactory = NULL; do
{
//遍历所有的工厂类,判断返回请求成功的riid
*ppv = NULL;
poCClassFactory = new CFactoryfromMysql();
if (poCClassFactory)
{
if (S_OK == poCClassFactory->QueryInterface(riid, ppv))
{
break;
}
else
{
poCClassFactory->Release();
}
} poCClassFactory = new CFactoryfromAccess();
if (poCClassFactory)
{
if (S_OK == poCClassFactory->QueryInterface(riid, ppv))
{
break;
}
else
{
poCClassFactory->Release();
}
} hr = CLASS_E_CLASSNOTAVAILABLE;
} while (FALSE); return hr;
}

前面说过COM组件技术中要求从QueryInterface()中返回接口对象,IClassFactory 自然也一样,所以要挨个调用现有工厂的 QueryInterface 函数,取到riid对应的工厂对象,这里为了能让大家看清楚,就未作优化了。可能有同学会问,为什么不写成简单工厂的方式,根据riid返回不同的工厂对象,这是由于将判断放到工厂类内部,可以给工厂类更大的灵活性,允许工厂内部做一些检查或其他工作,决定是否返回工厂对象,同时,也保持了和COM接口对象请求的一致性。
2.4、COM组件的使用
2.4.1 CLSID和IID的定义汇总

// {377C4A5C-52CB-4557-A9E5-A57018B34197}
static const GUID IID_IEmployee =
{ 0x377c4a5c, 0x52cb, 0x4557, { 0xa9, 0xe5, 0xa5, 0x70, 0x18, 0xb3, 0x41, 0x97 } }; // {940E7C36-8472-46E9-BE93-6C1F53699A1D}
static const GUID IID_IDepartment =
{ 0x940e7c36, 0x8472, 0x46e9, { 0xbe, 0x93, 0x6c, 0x1f, 0x53, 0x69, 0x9a, 0x1d } }; // {094EF4B6-406F-4CDC-A036-DBFC9E163196}
static const GUID IID_IFactoryfromMysql =
{ 0x94ef4b6, 0x406f, 0x4cdc, { 0xa0, 0x36, 0xdb, 0xfc, 0x9e, 0x16, 0x31, 0x96 } }; // {208C8F65-4F26-499D-A075-C0A6DFC312B1}
static const GUID IID_IFactoryfromAccess =
{ 0x208c8f65, 0x4f26, 0x499d, { 0xa0, 0x75, 0xc0, 0xa6, 0xdf, 0xc3, 0x12, 0xb1 } }; // {712CE48E-F1B4-4C5D-8BA8-6E367C4D6422}
static const GUID CLSID_IDataBase =
{ 0x712ce48e, 0xf1b4, 0x4c5d, { 0x8b, 0xa8, 0x6e, 0x36, 0x7c, 0x4d, 0x64, 0x22 } };

2.4.2 注册组件
   在cmd中输入Regsvr32 DataBaseCom.dll即可完成注册
2.4.3 调用COM组件

void	Test()
{
CoInitialize(NULL); HRESULT hr = S_OK;
IID stFactoryIID = IID_IFactoryfromAccess; //更换此处即可
IClassFactory* poIClassFactory = NULL; //取工厂类对象
hr = CoGetClassObject(CLSID_IDataBase, CLSCTX_INPROC_SERVER, NULL, stFactoryIID, (void**)&poIClassFactory); if (SUCCEEDED(hr))
{
IEmployee* poIEmployee = NULL;
if (SUCCEEDED(poIClassFactory->CreateInstance(NULL, IID_IEmployee, (void**)&poIEmployee)))
{
Employee stEmployee; stEmployee.nID = 1;
stEmployee.tstrName = _T("Jim");
poIEmployee->InserttoDB(stEmployee);
poIEmployee->Release();
}
else
{
printf("Get IEmployee fail!!!\n");
} IDepartment* poIDepartment = NULL;
if (SUCCEEDED(poIClassFactory->CreateInstance(NULL, IID_IDepartment, (void**)&poIDepartment)))
{
Department stDepartment; stDepartment.nID = 2;
stDepartment.tstrDepartmentName = _T("Marketing");
stDepartment.tstrManager = _T("Jim");
poIDepartment->InserttoDB(stDepartment);
poIDepartment->Release();
}
else
{
printf("Get IDepartment fail!!!\n");
} poIClassFactory->Release();
}
else
{
printf("Get IClassFactory fail!!!\n");
} CoUninitialize();
}

组件调用先通过CoGetClassObject()取工厂对象,组件注册后会将自身的CLSID和路径写入注册表中CoGetClassObject()实际通过 CLSID_IDataBase 找到Dll的路径并加载,然后调用DllGetClassObject()传入工厂类的IID取得工厂类对象,然后调用工厂类对象的CreateInstance()函数传入COM接口对象的IID取得用户需要的COM接口对象,改用COM组件技术实现后,如果需要更换数据库,只需要改变“IID stFactoryIID = IID_IFactoryfromAccess;”的IID即可,IID实际上是一串数字,我们可以放到配置文件、XML、注册表等其他位置,让客户端程序去加载。如果有新的数据类型要加入,比如SQL Server,我们在组件中加入后,客户端只要改变工厂类IID的配置文件即可,无需改动客户端代码,完美体现开放—封闭原则。

COM组件在注册表的CLSID和路径

由于代码较多,已将整个解决方案打包上传,代码在VS2010下编译通过,测试前在cmd中输入“Regsvr32 DataBaseCom.dll”即可完成注册,然后运行Console.exe即可看到运行结果
代码下载地址:http://download.csdn.net/detail/gufeng99/8806425

大话设计模式C++版——工厂模式在COM中的典型应用的更多相关文章

  1. 大话设计模式C++版——工厂方法模式

    工厂方法模式是以简单工厂模式为基础的,如果未了解简单工厂模式的同学可先浏览<大话设计模式C++版——简单工厂模式>.在简单工厂模式中,提到过简单工厂模式的缺陷,即违背了开发—封闭原则,其主 ...

  2. 大话设计模式C++版——代理模式

    本篇开始前先发个福利,程杰的<大话设计模式>一书高清电子版(带目录)已上传至CSDN,免积分下载. 下载地址:http://download.csdn.net/detail/gufeng9 ...

  3. 读《大话设计模式》——应用工厂模式的"商场收银系统"(WinForm)

    要做的是一个商场收银软件,营业员根据客户购买商品单价和数量,向客户收费.两个文本框,输入单价和数量,再用个列表框来记录商品的合计,最终用一个按钮来算出总额就可以了,还需要一个重置按钮来重新开始. 核心 ...

  4. 《大话设计模式》——简单工厂模式(Python版)

    简单工厂模式(Simple Factory Pattern):是通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类. 例: 使用Python设计一个控制台计算器,要求输入两个数 ...

  5. 大话设计模式C++版——建造者模式

    日常做菜的过程中,经常会有忘记放盐或者放2次盐的经历,最后导致好好的一盘菜让大家无从下口.这个时候就需要用到建造者模式来规范炒菜的过程,来保证每一道菜都会经历加油.放食物.放盐.放味精这4道基本的工序 ...

  6. 大话设计模式C++版——抽象工厂模式

    前面说过,简单工厂模式是最基础的一种设计模式,那以工厂命名的设计模式就是23种设计模式中最多的一种,他们一脉相承,一步一步进化而来,这里就是其中的最后一种——抽象工厂模式(Abstract Facto ...

  7. 设计模式之抽象工厂模式(Abstract Factory Pattern)

    一.抽象工厂模式的由来 抽象工厂模式,最开始是为了解决操作系统按钮和窗体风格,而产生的一种设计模式.例如:在windows系统中,我们要用windows设定的按钮和窗体,当我们切换Linux系统时,要 ...

  8. 大话设计模式C++版——简单工厂模式

    简单工厂模式应该是所有设计模式中最简单,也最基础的一种模式,以下是一个简单的采用工厂模式写一个加减法的计算器. 1.抽象接口类——依赖倒转原则(高层和底层都要依赖于抽象,针对接口编程) class I ...

  9. 大话设计模式C++版——表驱动法改造简单工厂

    上回<大话设计模式C++版——简单工厂模式>中指出了简单工厂模式的缺陷,即违背了开发—封闭原则,其主要原因是由于switch的判断结构的使用,使修改或添加新的对象时需要改动简单工厂类的代码 ...

随机推荐

  1. 正确的前端传后台json方式

    DEMO: var data=JSON.stringify({"page": {"pagenow": 1,"pagesize": 20},& ...

  2. Validating HTTP data with Play

    Validations ensure that the data has certain values or meets specific requirements. You can use vali ...

  3. 经典 HTML5 & Javascript 俄罗斯方块游戏

    Blockrain.js 是一个使用 HTML5 & JavaScript 开发的经典俄罗斯方块游戏.只需要复制和粘贴一段代码就可以玩起来了.最重要的是,它是响应式的,无论你的显示屏多么宽都能 ...

  4. 【HTML】字符(Glyphs)收集

    Special Characters " " " quotation mark u+0022 ISOnum p:before { content:"\0022& ...

  5. 【javascript激增的思考01】模块化编程

    前言 之前我做过一个web app(原来可以这么叫啦),在一个页面上有很多小窗口,每个小窗口都是独立的应用,比如: ① 我们一个小窗口数据来源是腾讯微博,需要形成腾讯微博app小窗口 ② 我们一个小窗 ...

  6. 调用MyFocus库,简单实现二十几种轮播效果

    一.首先点击这里下载myFocus库文件,标准文件库就行了,很小仅仅1.4M. myFocus库有以下的好处: a . 文件小巧却高效强大,能够实现二十几种轮播的效果. b . 极其简单的使用,只需要 ...

  7. 微信浏览器或各种移动浏览器上:active伪类做的触觉反馈失效

    在做移动端页面的时候,会发现PC上那种:hover的效果是不管用了的,但又要给用户一个点击反馈怎么办呢?我管它叫触觉反馈. 细心点就会发现浏览器有自带了一点触觉反馈,在点击a.button.input ...

  8. javascript的浅拷贝和深拷贝

    1.浅拷贝:复制一份引用,所有引用对象都指向一份数据,并且都可以修改这份数据. 2.深拷贝(复杂):复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制. 这里画一个简单的图来加深理解: ...

  9. DevExpress 13.1.8全面支持VS2013

    界面套包DevExpress 13.1.8重磅来袭.从这个版本开始所有.NET控件均正式支持VS2013,当然还有很多其他更新,下面是部分更新内容: DevExpress所有.NET控件: 正式支持V ...

  10. 使用cocoaPods一键集成第三方登录(新浪微博,qq,微信)

    第三方登录是现在app很常用的功能,而这个功能我已经写过两三次了...每次都写大同小异的代码真的是很痛苦,而且每次都要根据说明去添加那些依赖库,配置linkFlag什么的,完全是体力活,所以一直想把这 ...