C++中接口与实现分离的技术 ZZ
最简单清晰的例子:http://www.cnblogs.com/maoye/archive/2010/03/19/1690183.html
接口与实现分离
为什么这样设计?
主要原因是保持接口的稳定,而且封装性更好。类的实现细节跟其他类的联系都隐藏起来了。
具体实现
Database.h
Create的实现其实是调用Impl的实现。但在头文件中只需要CDatabaseImpl的声明。
#ifndef _DATABASE_H_
#define _DATABASE_H_
class CDatabaseImpl;
class CDatabase
{
public:
CDatabase();
~CDatabase();
int Create(const char* pName);
private:
CDatabaseImpl *m_pImpl;
}; #endif
Database.cpp
#include "Database.h"
#include "DatabaseImpl.h" CDatabase::CDatabase()
{
m_pImpl = new CDatabaseImpl;
}
CDatabase::~CDatabase()
{
if (m_pImpl)
delete m_pImpl;
m_pImpl = NULL;
}
int CDatabase::Create(const char* pName)
{
return m_pImpl->Create(pName);
}
DatabaseImpl.h
#ifndef DATABASE_IMPL_H
#define DATABASE_IMPL_H
#include <stdio.h>
class CDbMainPart
{
public:
int CreateMain()
{
printf("Main Created\n");
}
};
class CDbMinorPart
{
public:
int CreateMinor()
{
printf("Minor Created\n");
} };
class CDatabaseImpl
{
public:
CDatabaseImpl(){}
~CDatabaseImpl(){}
int Create(const char* pName);
private:
CDbMinorPart m_MinorPart;
CDbMainPart m_MainPart;
};
#endif
DatabaseImpl.cpp
#include <stdio.h>
#include "DatabaseImpl.h" int CDatabaseImpl::Create(const char* pName)
{
printf("[%s] Created\n",pName);
m_MainPart.CreateMain();
m_MinorPart.CreateMinor();
return ; }
main.cpp
#include "Database.h" int main()
{
CDatabase db;
db.Create("Longshine");
return ;
}
================================================================================================
在用C++写要导出类的库时,我们经常只想暴露接口,而隐藏类的实现细节。也就是说我们提供的头文件里只提供要暴露的公共成员函数的声明,类的其他所有信息都不会在这个头文件里面显示出来。这个时候就要用到接口与实现分离的技术。
下面用一个最简单的例子来说明。
类ClxExp是我们要导出的类,其中有一个私有成员变量是ClxTest类的对象,各个文件内容如下:
lxTest.h文件内容:
{
public:
ClxTest();
virtual ~ClxTest();
void DoSomething();
};
lxTest.cpp文件内容:
#include <iostream>
using namespace std;
ClxTest::ClxTest()
{
}
ClxTest::~ClxTest()
{
}
void ClxTest::DoSomething()
{
cout << "Do something in class ClxTest!" << endl;
}
lxExp.h文件内容:
class ClxExp
{
public:
ClxExp();
virtual ~ClxExp();
void DoSomething();
private:
ClxTest m_lxTest;
void lxTest();
};
lxExp.cpp文件内容:
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文件内容:
class ClxImplement
{
public:
ClxImplement();
~ClxImplement();
void DoSomething();
private:
ClxTest m_lxTest;
void lxTest();
};
lxImplement.cpp文件内容:
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 "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文件),而把类的具体实现交给其他程序员开发。
======================================================================================================================
我在今年2月份写了篇《C++中接口与实现分离的技术》的文章,用一个很简单的例子说明了在C++中接口与实现分离的好处及实现方法。很荣幸,这篇文章被推荐到了CSDN的首页并被多家网站转载。
可是当时写那篇文章的时候,没有考虑到类与类之间的继承关系。下面我就来具体的谈谈这个方面。
还是以上面提到的那篇文章中的例子来说明。
执行类:
lxImplement.h文件内容:
class ClxImplement
{
public:
ClxImplement();
~ClxImplement();
void DoSomething();
private:
ClxTest m_lxTest;
void lxTest();
};
lxImplement.cpp文件内容:
ClxImplement::ClxImplement()
{
}
ClxImplement::~ClxImplement()
{
}
void ClxImplement::lxTest()
{
m_lxTest.DoSomething();
}
void ClxImplement::DoSomething()
{
lxTest();
}
接口类:
lxExp.h文件内容:
class ClxImplement;
class ClxExp
{
public:
ClxExp();
virtual ~ClxExp();
void DoSomething();
private:
// 声明一个类ClxImplement的指针,不需要知道类ClxImplement的定义
ClxImplement *m_pImpl;
};
lxExp.cpp文件内容:
#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中要调用基类的方法,那上面的方案就不行了。比如说,类ClxExp的基类是下面的样子:
{
public:
ClxInF();
virtual ~ClxInF();
bool InitSet();
virtual void DoSomething();
};
相应的类ClxExp的声明变成了如下的形式:
{
public:
ClxExp();
virtual ~ClxExp();
void DoSomething();
private:
ClxImplement *m_pImpl;
};
现在, 假设我们必须在类ClxExp的DoSomething()方法中根据InitSet()的返回值来确定是否执行操作。最简单的实现方法是把类ClxExp的DoSomething()方法改成下面的样子:
{
if (InitSet())
m_pImpl->DoSomething();
}
可是如果这样的话,接口与实现就没有彻底的分离,因为实现细节被暴露到了接口类中。为了避免这种情况发生,我们就必须把对基类ClxInF的方法InitSet()调用放到执行类ClxImplement当中。可是怎么在执行类ClxImplement当中调用接口类ClxExp的基类ClxInF的方法呢?其实很简单,因为类ClxExp是类ClxInF的子类,那么它也就继承了类ClxInF的方法,只要把类ClxExp的this指针传给类ClxImplement,就可以通过这个指针来调用类ClxExp的方法,当然也可以调用类ClxExp从基类ClxInF继承来的方法。下面是修改后的代码:
lxImplement.h文件内容:
// 包含声明类ClxExp的头文件
#include "lxExp.h"
class ClxImplement
{
public:
// 构造函数,传入类的ClxExp的指针
ClxImplement(ClxExp *plxExp);
~ClxImplement();
void DoSomething();
private:
ClxTest m_lxTest;
// 定义一个类ClxExp的指针,可以通过该指针调用类ClxExp从基类继承下来的方法
ClxExp *m_plxExp;
void lxTest();
};
lxImplement.cpp文件内容:
ClxImplement::ClxImplement(ClxExp *plxExp)
{
m_plxExp = plxExp;
}
ClxImplement::~ClxImplement()
{
}
void ClxImplement::lxTest()
{
m_lxTest.DoSomething();
}
void ClxImplement::DoSomething()
{
if (m_plxExp->InitSet())
lxTest();
}
对于类ClxExp来说,只要修改一下它的构造函数就行了,其他都不用修改。
{
m_pImpl = new ClxImplement(this);
}
这样,我们就解决了前面所提到的问题。
当然,也许有人会说,让类ClxImplement也从类ClxInF继承不是更简单吗?那样就可以在类ClxImplement中直接调用类ClxInF的方法,也不用添加什么代码。可是我们知道公有继承是的子类与基类是IS-A的关系。也就是说子类是一种基类,就像说轿车是一种汽车一样。可是,在我们例子中,类ClxImplement只是类ClxExp的一个执行类而已,跟类ClxExp的基类ClxInF没有一点儿关系,更不要说是一种ClxInF了。所以不能让类ClxImplement从类ClxInF继承。
ref: http://blog.csdn.net/starlee/article/details/873489
==============================================================================================================================================================================================================================================
另外,这篇文章也很好:
http://www.cppblog.com/mzty/archive/2007/08/06/29441.html
C++中接口与实现分离的技术 ZZ的更多相关文章
- ZT 接口和实现分离
什么叫接口和实现分离,如何实现 [问题点数:20分,结帖人heronism] http://bbs.csdn.net/topics/310212385 http://blog.csdn.net/sta ...
- Java中接口和抽象类的比較
Java中接口和抽象类的比較-2013年5月写的读书笔记摘要 1. 概述 接口(Interface)和抽象类(abstract class)是 Java 语言中支持抽象类的两种机制,是Java程序设计 ...
- 关于 C# 中接口的一些小结
< 关于 C# 中“接口”的一些小结 > 对于 C# 这样的不支持多重继承的语言,很好的体现的层次性,但是有些时候多重继承的确有一些用武之地. 比如,在 Stream 类 . 图形设备 ...
- Java中接口的作用
转载于:https://www.zhihu.com/question/20111251 困惑:例如我定义了一个接口,但是我在继承这个接口的类中还要写接口的实现方法,那我不如直接就在这个类中写实现方法岂 ...
- PHP面向对象学习五 类中接口的应用
类中接口的应用 接口:一种成员属性全部为抽象的特殊抽象类,在程序中同为规范的作用 抽象类:1.类中至少有一个抽象方法.2.方法前需要加abstract 接口: 1.类中全部为抽象方法,抽象方法前不 ...
- Javascript模板及其中的数据逻辑分离思想(MVC)
#Javascript模板及其中的数据逻辑分离思想 ##需求描述 项目数据库的题目表描述了70-120道题目,并且是会变化的,要根据数据库中的数据描述,比如,选择还是填空题,是不是重点题,题目总分是多 ...
- Indri中的动态文档索引技术
Indri中的动态文档索引技术 戴维 译 摘要: Indri 动态文档索引的实现技术,支持在更新索引的同时处理用户在线查询请求. 文本搜索引擎曾被设计为针对固定的文档集合进行查询,对不少应用来说,这种 ...
- 阿里巴巴开源项目:分布式数据库同步系统otter(解决中美异地机房) - agapple - ITeye技术网站
阿里巴巴开源项目:分布式数据库同步系统otter(解决中美异地机房) - agapple - ITeye技术网站 阿里巴巴开源项目:分布式数据库同步系统otter(解决中美异地机房)
- OC中给我们提供的一个技术:谓词(NSPredicate).note
OC中给我们提供的一个技术:谓词(NSPredicate)OC中的谓词操作是针对于数组类型的,他就好比数据库中的查询操作,数据源就是数组,这样的好处是我们不需要编写很多代码就可以去操作数组,同时也起到 ...
随机推荐
- Linux网络编程服务器模型选择之循环服务器
在网络程序里面,通常都是一个服务器处理多个客户机,为了出个多个客户机的请求,服务器端的程序有不同的处理方式.本节开始介绍Linux下套接字编程的服务器模型选择,主要包括循环服务器模型.并发服务器模型. ...
- Stopwatch类学习
1.概述:给一条大MSDN的链接关于Stopwatch类最详细的教程 ,然后看着教程自己手动敲一边,加深映象,好记性不如烂键盘,哈哈,开个玩笑! 2.类位置:这个类在哪里,这个是重点,虽然C#IDE很 ...
- 数据库~Mysql里的Explain说明
对于mysql的执行计划可以在select前添加Explain来实现,它可以告诉我们你的语句性能如何. 下面是对explain的具体说明,也都是官方的,以后进行参考. id SELECT识别符.这是S ...
- python-广播
#!/usr/bin/python #coding=utf-8 #广播端 import sys,socket import time s=socket.socket(socket.AF_INET,so ...
- PHP之string之str_word_count()函数使用
str_word_count (PHP 4 >= 4.3.0, PHP 5, PHP 7) str_word_count - Return information about words use ...
- linux进程间的网络通信
一.进程是如何进行网络通信的?socket通信的过程? 同一机器上的不同进程之间的通信方式有很多种,主要使用消息传递或共享内存.而跨网络的进程是几乎都是使用socket通信,例如web服务器,QQ. ...
- mysql时间统计,查询月份,周数据
在mysql数据库中,常常会遇到统计当天的内容.例如,在user表中,日期字段为:log_time 统计当天 sql语句为: select * from user where date(log_tim ...
- WP的万能小应用时钟表
哎,只能说现在是越来越不行了,已经近一年没写C#的代码了,我居然隐隐有看不懂自己代码的趋势了,真伤! 我突然想起当年寒假里面为了,准备微软创新杯大赛所做的一些小应用,哈哈,于是我就拿出来显摆一下喽! ...
- 时间格式转换成JUN.13,2017
SimpleDateFormat sdf = new SimpleDateFormat("MMM.dd,yyyy", Locale.ENGLISH); String negotia ...
- nodejs添加jsonwebtoken验证
具体使用模块: 使用compression压缩处理请求响应.cors模块添加跨域.helmet安全模块.body-parser解析请求参数.jsonwebtoken用于生成及校验token.使用内置c ...