C++中接口与实现分离的技术

在用C++写要库时,我们经常只想暴露接口,而实现细节。也就是提供的文件里只提供要暴露的公共成函数的声明,的其他所有信息都不会在文件里面示出来。候就要用到接口与实现分离的技。《COM技术内幕》 §7 —— 类厂、COM

CoCreateInstance
的声明:

HRESULT __stdcall CoCreateInstance(
const CLSID& clsid,
IUnknown* pIUnknownOuter,
DWORD dwClsContext,
const IID& iid,
void** ppv
);


CoCreateInstance
有四个输入参数和一个输出参数。第一个参数是待创建组件的CLSID。第二个参数是用于聚合组件的。第三个参数的作用是限定所创建的组件的执行上下文。第四个参数iid为组件上待使用的接口的IIDCoCreateInstance将在最后一个参数中返回此接口的指针。通过将一个IID传给CoCreateInstance,客户将无需在创建组件之后去调用其QueryInterface函数。

CoCreateInstance
的使用:

// Create Component

IX* pIX = NULL;

HRESULT hr = :: CoCreateInstance( CLSID_Component1,

NULL,

CLSCTX_INPROC_SERVER,

IID_IX,

(void**)&pIX);

If(SUCCEEDED(hr))

{

pIX->Fx();

pIX->Release();

}


传给CoCreateInstance的最后两个参数同传给QueryInterface的参数是一样的。

CoCreateInstance
的第三个参数dwClsContext(类上下文)可以控制所创建的组件是在与客户相同的进程中运行,还是在不同的进程中运行,或者是在另外一台机器上运行。其取值为下列各值的组合:

CLSCTX_INPROC_SERVER
客户希望创建在同一进程中运行的组件。为能够同客户在同一进程中运行,组建必须是在DLL中实现的。

CLSCTX_INPROC_HANDLER
客户希望创建进程中处理器。一个进程甲处理器实际上是一个只实现了某个组件一部分的进程中组件。该组件的其他部分将由本地或远程服务器上的某个进程外组件实现。

CLSCTX_LOCAL_SERVER
客户希望创建一个在同一机器上的另外一个进程中运行的组件。本地服务器是由EXE实现的。

CLSCTX_REMOTE_SERVER
客户希望创建一个在远程机器上运行的组件。此标志需要分布式COM正常工作。


与《COM原理与应用》中第三章的例子程序相对比,《COM技术内幕》第七章的程序的共同的部分:

1)客户程序(有待改进):

int main
()

{

// CoInitialize
();

// CoCreateInstance
();

// CoUninitialize
();

}


2COM程序

// Interfaces Definition

// Component Class Declaration

// Component Class Functions Implementation for IUnknown Interfaces

// {

// QueryInterface(const IID& iid, void** ppv)


// AddRef( )


// Release( )


// }

// Component Class Functions Implementation for IClassFactory Interfaces

// {

// IUnknown Interface

// QueryInterface(const IID& iid, void** ppv)


// AddRef( )


// Release( )


// IClassFactory Interface

// CreateInstance
IUnknown *pUnknownOuter, const IID& iid, void **ppv);

// LockServer(BOOL bLock)


// }

// Exported functions

// DllCanUnloadNow( ); // Can DLL Unload Now?

// DllGetClassObject(const CLSID& clsid, const IID& iid, void **ppv); //Get Class Factory

// Server Registation

// DllRegisterServer( );

// Server Unregistation

// DllUnregisterServer( );

// DLL Module Information

// DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
 

    下面用一个最简单的例子来明。
    ClxExp是我出的,其中有一个私有成员变量是ClxTest象,各个文件内容如下:
    lxTest.h文件内容:

class ClxTest  
{
public:
    ClxTest();
    
virtual ~ClxTest();
 
    
void DoSomething();
};

    lxTest.cpp文件内容:

#include "lxTest.h"

#include <iostream>
using namespace std;

ClxTest::ClxTest()
{
}

ClxTest::~ClxTest()
{
}

void ClxTest::DoSomething()
{
    cout << "Do something in class ClxTest!" 
<< endl;
}

    lxExp.h文件内容:

#include "lxTest.h"

class ClxExp  
{
public:
    ClxExp();
    
virtual ~ClxExp();

    
void DoSomething();

private:
    ClxTest m_lxTest;

    
void lxTest();
};

    lxExp.cpp文件内容:

#include "lxExp.h"

ClxExp::ClxExp()
{
}

ClxExp::~ClxExp()
{
}

//  实该方法在里并没有必要,我这样只是
void ClxExp::lxTest()
{
    m_lxTest.DoSomething(); 
}

void ClxExp::DoSomething()
{
    lxTest();
}

    能使用我ClxExp,我提供lxExp.h文件,这样类ClxExp的私有成也暴露了。而且,仅仅提供lxExp.h文件是不的,因lxExp.h文件includelxTest.h文件,在这种情况下,我们还要提供lxTest.h文件。那ClxExp实现细节就全暴露了。另外,当我们对类ClxTest做了修改(如添加或除一些成员变量或方法),我们还更新lxTest.h文件,而个文件是跟接口无的。如果ClxExp里面有很多像m_lxTest象的,我就要提供NlxTest.h文件,而且其中任何一个有改,我都要更新文件。有一点就是用这种情况下必须进重新编译上面是非常小的一个例子,重新编译时间可以忽略不。但是,如果ClxExp被用大量使用的,那在一个大目中,重新编译候我就有时间可以去喝杯咖啡什的了。当然上面的种种情况不是我想看到的!你也可以想像一下用在自己程序不用改的情况下要不停的更新文件和编译时,他心里些什。其实对,他ClxExp的接口DoSomething()方法。那我才能只暴露ClxExpDoSomething()方法而不又生上面所的那些问题呢?答案就是--接口与实现的分离。我可以让类ClxExp接口,而把实现放在另外一个里面。下面是具体的方法:
    首先,添加一个实现类ClxImplement实现ClxExp的所有功能。注意:ClxImplement有着跟ClxExp的公有成函数,因的接口要完全一致
    lxImplement.h文件内容:

#include "lxTest.h"

class ClxImplement  
{
public:
    ClxImplement();
    ~ClxImplement();

    
void DoSomething();

private:
    ClxTest m_lxTest;

    
void lxTest();
};

    lxImplement.cpp文件内容:

#include "lxImplement.h"

ClxImplement::ClxImplement()
{
}

ClxImplement::~ClxImplement()
{
}

void ClxImplement::lxTest()
{
    m_lxTest.DoSomething();
}

void ClxImplement::DoSomething()
{
    lxTest();
}

    然后,修改ClxExp
    
修改后的lxExp.h文件内容:

//  前置声明
class ClxImplement;

class ClxExp  
{
public:
    ClxExp();
    
virtual ~ClxExp();

 
void DoSomething();

private:
    
//  声明一个ClxImplement的指,不需要知道ClxImplement的定
    ClxImplement *m_pImpl;
};

    修改后的lxExp.cpp文件内容:

//  里包含ClxImplement的定义头文件
#include "lxImplement.h"

ClxExp::ClxExp()
{
    m_pImpl = 
new ClxImplement;
}

ClxExp::~ClxExp()
{
    
if (m_pImpl)
        
delete m_pImpl;
}

void ClxExp::DoSomething()
{
    m_pImpl->DoSomething();
}

    上面的方法就实现ClxExp的接口与实现的分离。注意两个文件中的注ClxExp里面声明的只是接口而已,而真正的实现细节藏到了ClxImplement里面。了能在ClxExp中使用ClxImplement而不include文件lxImplement.h,就必有前置声明class ClxImplement,而且只能使用指向ClxImplement象的指,否就不能通过编译。在文件的候,我只需提供一个文件lxExp.h就行了,不会暴露ClxExp的任何实现细节。而且我们对类ClxTest的任何改,都不需要再更新文件(当然,文件是要更新的,但是这种情况下用不用重新编译)。这样有一个好就是,可以在分析段由系分析或者高程序来先把的接口定好,甚至可以把接口代写好(例如上面修改后的lxExp.h文件和lxExp.cpp文件),而把的具体实现其他程序员开发

评论:

是接口-实现分离的一方法,也叫做句柄有一个更好的方法是通声明一个抽象接口,实现这个接口,使用更简单,易于理解。

class ISomething {
public:
virtual int Method1() = 0;
virtual int Method2() = 0;
};

class CSthImpl : public ISomething {
public:
// ISomething methods
int Method1();
int Method2();

public:
CSthImpl();
~CSthImpl();

private:
...
};

更喜Iface 的方法
上,COM的基本原理就是这样,只是它用的是interface关键字,事上就是struct,本上就是只含有虚方法的抽象类。

于小型工程来, 这么用其很不划算, 一旦接口, 你需要修改:
实现类
句柄
接口

如果一定要把接口做的很干, 不如直接用抽象基, 这样接口只要修改接口抽象基

如果只是希望接口不依其他文件, 那在接口中所有象都动态生成就可以了

class ClxTest;
class ClxExp
{
public:
ClxExp();
virtual ~ClxExp();
void DoSomething();

private:
ClxTest *m_plxTest;
void lxTest();
};

自己的一点感, 反正我很少用这种句柄设计

我没弄明白ClxImplement的作用,
如果仅仅是不想暴露ClxTest,那可以在lxExp.h文件中ClxExp之前直接前置声明
class ClxTest,
然后在申明中定一个私有成
ClxTest*m_pTest,
这样有什么问题呢?
这样是不是也不用向用提供lxTest.h文件,因而就不会暴露lxTest.h文件?


    

2007-11-17 15:32:16作者回
如果没用ClxImplement,那么类ClxExp的私有方法也会被用看到。而且如果ClxExp实现有任何化,用都要重新编译自己的程序。比如,添加了一个私有成或者函数(用只会使用ClxExp的公有成,新添加的跟用没有任何系),这样就必更新ClxExp文件,而且要重新编译自己的程序。而有了ClxImplement就可以避免一切。

猜你喜欢

转载自blog.csdn.net/dengrk/article/details/1942563