C++——接口与实现分离技术

本文介绍了一种在C++中实现接口与实现分离的方法,通过将类的接口与实现拆分为两个不同的类,可以有效地隐藏类的实现细节,减少对外部改动的依赖,并简化用户的编译过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    在用C++写要导出类的库时,我们经常只想暴露接口,而隐藏类的实现细节。也就是说我们提供的头文件里只提供要暴露的公共成员函数的声明,类的其他所有信息都不会在这个头文件里面显示出来。这个时候就要用到接口与实现分离的技术。
    下面用一个最简单的例子来说明。
    类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文件include了lxTest.h文件,在这种情况下,我们还要提供lxTest.h文件。那样ClxExp类的实现细节就全暴露给用户了。另外,当我们对类ClxTest做了修改(如添加或删除一些成员变量或方法)时,我们还要给用户更新lxTest.h文件,而这个文件是跟接口无关的。如果类ClxExp里面有很多像m_lxTest那样的对象的话,我们就要给用户提供N个像lxTest.h那样的头文件,而且其中任何一个类有改动,我们都要给用户更新头文件。还有一点就是用户在这种情况下必须进行重新编译!上面是非常小的一个例子,重新编译的时间可以忽略不计。但是,如果类ClxExp被用户大量使用的话,那么在一个大项目中,重新编译的时候我们就有时间可以去喝杯咖啡什么的了。当然上面的种种情况不是我们想看到的!你也可以想像一下用户在自己程序不用改动的情况下要不停的更新头文件和编译时,他们心里会骂些什么。其实对用户来说,他们只关心类ClxExp的接口DoSomething()方法。那我们怎么才能只暴露类ClxExp的DoSomething()方法而不又产生上面所说的那些问题呢?答案就是--接口与实现的分离。我可以让类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文件内容:

#include "lxExp.h"
//   在这里包含类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文件),而把类的具体实现交给其他程序员开发。

补充说明:

    这里实质是让ClxExp变成了一个handle calss, 这并不会改变它做的事,而是改变它做事的方式。
    另外一种制作handle calss的方式是用抽象基类 (interface class)实现,这里就不详述了,他是实现接口与实现分离的又一种方式,但与最小化文件编译依赖关系好像没有太大关系。而且我们可以发现,这两种方法实际上就是对应设计模式中的strategy和template模式。


    其实总归下来,如果不用接口与实现分离方法,要实现编译依赖最小化,一定要保证:
    1. 头文件中只用声明式,不给出实现细目,具体#include放在cpp中完成( 系统头文件可以直接包含,编译瓶颈通常不在这里。)
    2. 实现细目(成员变量)尽量使用指针。
    3. 参数尽量使用指针和引用

    如果要达到接口与实现的分离:
    1. 将class分成两个class, 一个用于接口,一个用于实现, 接口class持有实现class的指针
    2. 接口class不含有任何实现细目,将具体实现细目丢入实现class完成

转载自:http://blog.youkuaiyun.com/xtconan/archive/2009/06/03/4238760.aspx

           http://blog.youkuaiyun.com/starlee/archive/2006/02/27/610825.aspx

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值