类的接口与实现分离的技术

用C++写导出类库时,只暴露接口,隐藏类的实现细节。即:提供的头文件里只暴露的公共成员函数的声明,类的其他所有信息都不会在这个头文件里面显示出来。这个时候就要用到接口与实现分离的技术。

 

下面用一个最简单的例子来说明。

 

类CExp是我们要导出的类,其中有一个私有成员变量是CTest类的对象,各个文件内容如下:

 

///  
//Test.h文件内容:  

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


///  
//Test.cpp文件内容:   
///  

#include "Test.h"  
 
#include <iostream>  
using namespace std;  
 
CTest::CTest()  
{  
}  
 
CTest::~CTest()  
{  
}  
 
void CTest::DoSomething()  
{  
    cout << "Do something in class CTest!" << endl;  
}  
 
///   
//Exp.h文件内容:  

///    
#include "Test.h"  
 
class CExp    
{  
public:  
    CExp();  
    virtual ~CExp();  
 
    void DoSomething();  
 
private:  
    CTest m_Test;   
    void Test();  
};   
  
///  
//Exp.cpp文件内容:  

///   

 
#include "Exp.h"  
 
CExp::CExp()  
{  
}  
 
CExp::~CExp()  
{  
}  
 
//  其实该方法在这里并没有必要,这样只是为了说明调用关系  
void CExp::Test()  
{  
    m_Test.DoSomething();   
}  
 
void CExp::DoSomething()  
{  
    Test();  

 

///  
//Test.h文件内容:

///  

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

 

///  
//Test.cpp文件内容:

///  

#include "Test.h"

#include <iostream>
using namespace std;

CTest::CTest()
{
}

CTest::~CTest()
{
}

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

 

///

//Exp.h文件内容:

///  

#include "Test.h"

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

    void DoSomething();

private:
    CTest m_Test;

    void Test();
};

 

///  
//Exp.cpp文件内容:

///  

#include "Exp.h"

CExp::CExp()
{
}

CExp::~CExp()
{
}

//  其实该方法在这里并没有必要,这样只是为了说明调用关系
void CExp::Test()
{
    m_Test.DoSomething();
}

void CExp::DoSomething()
{
    Test();
}

 

为了让用户能使用我们的类CExp,我们必须提供Exp.h文件,这样类CExp的私有成员也暴露给用户了。而且,仅仅提供Exp.h文件是不够的,因为Exp.h文件include了Test.h文件,在这种情况下,我们还要提供Test.h文件。那样CExp类的实现细节就全暴露给用户了。另外,当我们对类CTest做了修改(如添加或删除一些成员变量或方法)时,我们还要给用户更新Test.h文件,而这个文件是跟接口无关的。如果类CExp里面有很多像m_Test那样的对象的话,我们就要给用户提供N个像Test.h那样的头文件,而且其中任何一个类有改动,我们都要给用户更新头文件。还有一点就是用户在这种情况下必须进行重新编译!上面是非常小的一个例子,重新编译的时间可以忽略不计。但是,如果类CExp被用户大量使用的话,那么在一个大项目中,重新编译的时候我们就有时间可以去喝杯咖啡什么的了。当然上面的种种情况不是我们想看到的!你也可以想像一下用户在自己程序不用改动的情况下要不停的更新头文件和编译时,他们心里会骂些什么。其实对用户来说,他们只关心类CExp的接口DoSomething()方法。那我们怎么才能只暴露类CExp的DoSomething()方法而不又产生上面所说的那些问题呢?答案就是--接口与实现的分离。我可以让类CExp定义接口,而把实现放在另外一个类里面。

 

下面是具体的方法:

  

      首先,添加一个实现类CImplement来实现CExp的所有功能。注意:类CImplement有着跟类CExp一样的公有成员

函数,因为他们的接口要完全一致

///  
//Implement.h文件内容:  

///   
 
#include "Test.h"  
 
class CImplement    
{  
public:  
    CImplement();  
    ~CImplement();  
 
    void DoSomething();  
 
private:  
    CTest m_Test;  
 
    void Test();  
};   
 
///  
//Implement.cpp文件内容:  

///   

 
#include "Implement.h"  
 
CImplement::CImplement()  
{  
}  
 
CImplement::~CImplement()  
{  
}  
 
void CImplement::Test()  
{  
    m_Test.DoSomething();  
}  
 
void CImplement::DoSomething()  
{  
    Test();  

 

///  
//Implement.h文件内容:

///  

//#include "Test.h"

///  

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

    void DoSomething();

private:
    CTest m_Test;

    void Test();
};

 

///  
//Implement.cpp文件内容:

///  

#include "Implement.h"

CImplement::CImplement()
{
}

CImplement::~CImplement()
{
}

void CImplement::Test()
{
    m_Test.DoSomething();
}

void CImplement::DoSomething()
{
    Test();
}

 

///  
//然后,修改类CExp。修改后的Exp.h文件内容:

///  
//  前置声明  
class CImplement;  
 
class CExp    
{  
public:  
    CExp();  
    virtual ~CExp();  
 
 void DoSomething();  
 
private:  
    //  声明一个类CImplement的指针,不需要知道类CImplement的定义  
    CImplement *m_pImpl;  
};  
 

///   
//修改后的Exp.cpp文件内容:  

//  在这里包含类CImplement的定义头文件  

/// 
#include "Implement.h"  
 
CExp::CExp()  
{  
    m_pImpl = new CImplement;  
}  
 
CExp::~CExp()  
{  
    if (m_pImpl)  
        delete m_pImpl;  
}  
 
void CExp::DoSomething()  
{  
    m_pImpl->DoSomething();  


//  前置声明
class CImplement;

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

 void DoSomething();

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

 

/// 

//   修改后的Exp.cpp文件内容:

//   在这里包含类CImplement的定义头文件

/// 
#include "Implement.h"

CExp::CExp()
{
    m_pImpl = new CImplement;
}

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

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

 

通过上面的方法就实现了类CExp的接口与实现的分离。请注意两个文件中的注释。类CExp里面声明的只是接口而已,而真正的实现细节被隐藏到了类CImplement里面。为了能在类CExp中使用类CImplement而不include头文件Implement.h,就必须有前置声明class CImplement,而且只能使用指向类CImplement对象的指针,否则就不能通过编译。在发布库文件的时候,我们只需给用户提供一个头文件Exp.h就行了,不会暴露类CExp的任何实现细节。而且我们对类CTest的任何改动,都不需要再给用户更新头文件(当然,库文件是要更新的,但是这种情况下用户也不用重新编译!)。这样做还有一个好处就是,可以在分析阶段由系统分析员或者高级程序员来先把类的接口定义好,甚至可以把接口代码写好(例如上面修改后的Exp.h文件和Exp.cpp文件),而把类的具体实现交给其他程序员开发。

上文还没有考虑到类与类之间的继承关系。下面我们就来具体的谈谈这个方面。

 

 

     还是以上面提到的那篇文章中的例子来说明。


 执行类:  
 

/// 
//Implement.h文件内容:  
///  
#include "Test.h"  
 
class CImplement    
{  
public:  
    CImplement();  
    ~CImplement();  
 
    void DoSomething();  
 
private:  
    CTest m_Test;  
 
    void Test();  
};  
 
 

/// 

//Implement.cpp文件内容:  

/// 
 
#include "Implement.h"  
 
CImplement::CImplement()  
{  
}  
 
CImplement::~CImplement()  
{  
}  
 
void CImplement::Test()  
{  
    m_Test.DoSomething();  
}  
 
void CImplement::DoSomething()  
{  
    Test();  
}  
 
  接口类:   
///

//Exp.h文件内容:  

/// 
 
//  前置声明  
class CImplement;  
 
class CExp    
{  
public:  
    CExp();  
    virtual ~CExp();  
 
 void DoSomething();  
 
private:  
    //  声明一个类CImplement的指针,不需要知道类CImplement的定义  
    CImplement *m_pImpl;  
};  
 

/// 
//  Exp.cpp文件内容:  

/// 
 
//  在这里包含类CImplement的定义头文件  
#include "Implement.h"  
 
CExp::CExp()  
{  
    m_pImpl = new CImplement;  
}  
 
CExp::~CExp()  
{  
    if (m_pImpl)  
        delete m_pImpl;  
}  
 
void CExp::DoSomething()  
{  
    m_pImpl->DoSomething();  

 


 执行类:

/// 

//  Implement.h文件内容:

/// 

#include "Test.h"

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

    void DoSomething();

private:
    CTest m_Test;

    void Test();
};

 

/// 

//  Implement.cpp文件内容:

/// 

#include "Implement.h"

CImplement::CImplement()
{
}

CImplement::~CImplement()
{
}

void CImplement::Test()
{
    m_Test.DoSomething();
}

void CImplement::DoSomething()
{
    Test();
}

  接口类:

/// 

//  Exp.h文件内容:

/// 

//  前置声明
class CImplement;

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

 void DoSomething();

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

/// 

//  Exp.cpp文件内容:

/// 

//  在这里包含类CImplement的定义头文件
#include "Implement.h"

CExp::CExp()
{
    m_pImpl = new CImplement;
}

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

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

 

但是,如果类CExp是另一个类的子类,而在类CExp中要调用基类的方法,那上面的方案就不行了。比如说,类CExp的基类是下面的样子:

view plaincopy to clipboardprint?
class CInF  
{  
public:  
    CInF();  
    virtual ~CInF();  
 
    bool InitSet();  
 
    virtual void DoSomething();  
}; 
class CInF
{
public:
    CInF();
    virtual ~CInF();

    bool InitSet();

    virtual void DoSomething();
};

 

相应的类CExp的声明变成了如下的形式:


class CExp : public CInF  
{  
public:  
    CExp();  
    virtual ~CExp();  
 
    void DoSomething();  
 
private:  
    CImplement *m_pImpl;  
}; 
class CExp : public CInF
{
public:
    CExp();
    virtual ~CExp();

    void DoSomething();

private:
    CImplement *m_pImpl;
};

 

现在,假设我们必须在类CExp的DoSomething()方法中根据InitSet()的返回值来确定是否执行操作。最简单的实现方法是把类CExp的DoSomething()方法改成下面的样子:
void CExp::DoSomething()  
{  
    if (InitSet())  
        m_pImpl->DoSomething();  

void CExp::DoSomething()
{
    if (InitSet())
        m_pImpl->DoSomething();
}

 

可是如果这样的话,接口与实现就没有彻底的分离,因为实现细节被暴露到了接口类中。为了避免这种情况发生,我们就必须把对基类CInF的方法InitSet()调用放到执行类CImplement当中。可是怎么在执行类CImplement当中调用接口类CExp的基类CInF的方法呢?其实很简单,因为类CExp是类CInF的子类,那么它也就继承了类CInF的方法,只要把类CExp的this指针传给类CImplement,就可以通过这个指针来调用类CExp的方法,当然也可以调用类CExp从基类CInF继承来的方法。下面是修改后的代码:

 

/// 

//Implement.h文件内容:  

/// 
 
#include "Test.h"  
// 包含声明类CExp的头文件  
#include "Exp.h"  
 
class CImplement    
{  
public:  
    //  构造函数,传入类的CExp的指针  
    CImplement(CExp *pExp);  
    ~CImplement();  
 
    void DoSomething();  
 
private:  
    CTest m_Test;  
    //  定义一个类CExp的指针,可以通过该指针调用类CExp从基类继承下来的方法  
    CExp *m_pExp;  
 
    void Test();  
};  
 

/// 
//  Implement.cpp文件内容:  

/// 
 
#include "Implement.h"  
 
CImplement::CImplement(CExp *pExp)  
{  
    m_pExp = pExp;  
}  
 
CImplement::~CImplement()  
{  
}  
 
void CImplement::Test()  
{  
    m_Test.DoSomething();  
}  
 
void CImplement::DoSomething()  
{  
    if (m_pExp->InitSet())  
        Test();  

 

/// 
//Implement.h文件内容:

/// 

#include "Test.h"
// 包含声明类CExp的头文件
#include "Exp.h"

class CImplement 
{
public:
    //  构造函数,传入类的CExp的指针
    CImplement(CExp *pExp);
    ~CImplement();

    void DoSomething();

private:
    CTest m_Test;
    //  定义一个类CExp的指针,可以通过该指针调用类CExp从基类继承下来的方法
    CExp *m_pExp;

    void Test();
};

 /// 

//  Implement.cpp文件内容:

/// 

#include "Implement.h"

CImplement::CImplement(CExp *pExp)
{
    m_pExp = pExp;
}

CImplement::~CImplement()
{
}

void CImplement::Test()
{
    m_Test.DoSomething();
}

void CImplement::DoSomething()
{
    if (m_pExp->InitSet())
        Test();
}

 

对于类CExp来说,只要修改一下它的构造函数就行了,其他都不用修改。

CExp::CExp()
{
    m_pImpl = new CImplement(this);
}

  这样,我们就解决了前面所提到的问题。

  当然,也许有人会说,让类CImplement也从类CInF继承不是更简单吗?那样就可以在类CImplement中直接调用类

CInF的方法,也不用添加什么代码。可是我们知道公有继承是的子类与基类是IS-A的关系。也就是说子类是一种基类,就

像说轿车是一种汽车一样。可是,在我们例子中,类CImplement只是类CExp的一个执行类而已,跟类CExp的基类

CInF没有一点儿关系,更不要说是一种CInF了。所以不能让类CImplement从类CInF继承。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/amossavez/archive/2009/07/29/4390652.aspx

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值