寒星轩

There are innumerable stars in the sky, the smallest is me!

李星ID:starlee
193261次访问,排名334好友0人,关注者46
欢迎大家访问我的Blog。
主要是C++,设计模式,面向对象设计方面的技术文章。
starlee的文章
原创 93 篇
翻译 0 篇
转载 45 篇
评论 280 篇
StarLee的公告
郑重声明

        本BLOG所发表的 原创文章,作者保留一切权利。必须经过作者本人同意后方可转载,并注名作者(StarLee)和出处(CSDN Blog)。
作者Email:
coolstarlee(at)sohu.com
最近评论
qzq:在C++中,friend是破坏封装性的,友元函数可以不受访问权限的限制而访问类的任何成员。在Singletion模式的例子代码中,这正是利用了友元函数的这个特性来访问类的私有构造函数来创建类在进程中的唯一对象的


如果这样的话,成员函数也是:
在C++中,成员函数 是破坏封装性的,成员函数可以不受访问权限的限制而访问类的任何成员。在Singletio……
qzq:[14.2] 友元破坏了封装吗?UPDATED!
[Recently made a bit more emphatic (on 4/01). Click here to go to the next FAQ in the "chain" of recent changes.]

如果被适当的使用,实际上可以增强封装。

当一个类的两部分会有不……
ray:其实你这篇文章很好,我看不懂,估计半个小时后,我能看懂,好多文章都很好,但是太抽象,我的观点是,把Dosomething ,改成eat,把Exp改成Dog,把抽象的东西实例化。
ll:鄙视……
cylx00:看过,没想到名字也有寓意,楼主想得很远.
文章分类
收藏
相册
友情链接
houdy的专栏
lijgame的专栏
lyrebing的专栏
禾青谷
存档
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
订阅到BlogLines
订阅到Yahoo
订阅到GouGou
订阅到飞鸽
订阅到Rojo
订阅到newsgator
订阅到netvibes

原创 C++中接口与实现分离的技术收藏

新一篇: 今天我Blog里面的一篇文章登上了CSDN的首页 | 旧一篇: 不用临时变量交换两个变量的值

    在用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文件),而把类的具体实现交给其他程序员开发。

发表于 @ 2006年02月27日 09:08:00|评论(loading...)|编辑

新一篇: 今天我Blog里面的一篇文章登上了CSDN的首页 | 旧一篇: 不用临时变量交换两个变量的值

评论

#北漂的天空 发表于2006-03-01 11:04:00  IP: 221.122.55.*
写的真好。不单c++可以这样子,c#也可以这样做的。
#nothing 发表于2006-03-01 17:50:00  IP: 159.226.219.*
Design pattern 里的pImpl不就是干这个的吗?
#nothing 发表于2006-03-01 18:02:00  IP: 159.226.219.*
no abstraction without penalty. 慎用!
#简单代码 发表于2006-03-01 18:53:00  IP: 221.222.74.*
这个并没有实现真正的接口,还需要提供类。
#Iface 发表于2006-03-03 22:20:00  IP: 221.217.18.*
这是接口-实现分离的一种方法,也叫做句柄类,还有一个更好的方法是通过声明一个抽象类做为接口,实现这个接口,使用更简单,易于理解。

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:
...
};
#malochj(水手) 发表于2006-03-05 21:09:00  IP: 220.248.87.*
嗯 这个方法很不错
#dynuaa 发表于2006-03-07 00:16:00  IP: 10.22.24.*
更喜欢Iface 的方法
事实上,COM的基本原理就是这样,只是它用的是interface关键字,事实上就是struct,本质上就是只含有虚方法的抽象类
#captainwh 发表于2006-03-08 08:13:00  IP: 218.27.67.*
对于小型工程来说, 这么用其实很不划算, 一旦接口发生变更, 你需要修改:
实现类
句柄类
接口类

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

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

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

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

自己的一点感觉, 反正我很少用这种句柄类的设计
#叶儿 发表于2006-03-08 10:51:00  IP: 202.115.65.*
不错,看懂了,值得学习~~~
#efijki 发表于2007-01-03 22:53:04  IP: 222.90.211.*
不错。句柄类的介绍在钱能的《C++ 程序设计教程》里有介绍
#yaochunnian_v1 发表于2007-11-16 20:55:10  IP: 222.240.165.*
我没弄明白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就可以避免这一切。
#hqwsdu 发表于2008-03-18 15:26:42  IP: 221.2.163.*
小弟初学不是太懂,想问一下在修改后的lxExp.cpp文件中是不是还应该包含原来的 那个 lxExp.h 头文件呢?谢了
2008-03-19 08:52:35作者回复
不用包含原来的那个lxExp.h头文件。其实修改后的文件跟修改前的文件完全是2个版本,没有任何联系。前面那个版本只是为了与后面的版本进行比较,来说明“接口与实现分离”的优点。
#孙阳东 发表于2008-04-30 10:37:48  IP: 218.66.48.*
"在发布库文件的时候,我们只需给用户提供一个头文件lxExp.h就行了,不会暴露类ClxExp的任何实现细节。"是吗?ClxImplement.h也要吧?ClxExp.cpp中不是也要include ClxImplemet.h?
2008-04-30 11:09:43作者回复
发布的时候只用提供头文件lxExp.h和相应的lib文件或者dll就行了,根本不会暴露ClxExp.cpp文件,当然也不用提供ClxImplement.h文件了。
#Demon__Hunter 发表于2008-05-01 15:09:16  IP: 202.118.2.*
偶一点还是不明白,当库升级的时候,如果提供的是lib文件,或者dll静态加载,那么使用者接到更新后的库,是不是很要重新编译下自己的程序啊?
2008-05-02 15:49:40作者回复
文章中已经说的很明白了,由于接口没有任何变化,更新库文件的时候,不用重新编译自己的程序。
#Demon__Hunter 发表于2008-05-02 18:49:28  IP: 202.118.2.*
接口没有变化,提供给用户的库是dll,而且dll在用户那是动态加载的话,用户不用重新编译自己的程序,这我也明白。但是如果用户那是dll静态加载,或者提供给用户的是lib,为什么不用重新编译哪?以我的理解,lib更改,或静态加载的dll一经改变,必须重新编译程序,以便使静态库的东西重新链接进exe中?虽然接口没变,但库中的一些方法变化了,应该是重新编译吧?这一点偶不是很明白~~~~
希望再讲明白一些~~~~~
谢谢!
2008-05-02 19:42:47作者回复
只有用lib文件的时候才需要重新编译。如果用dll,不管是静态加载还是动态加载,都不需要重新编译程序。
#ray 发表于2008-07-15 15:50:12  IP: 219.143.247.*
其实你这篇文章很好,我看不懂,估计半个小时后,我能看懂,好多文章都很好,但是太抽象,我的观点是,把Dosomething ,改成eat,把Exp改成Dog,把抽象的东西实例化。
发表评论  


登录
Csdn Blog version 3.1a
Copyright © StarLee