34.将文件间的编译依赖性降到最低。

对于一个大型程序,其结构是错综复杂的。当你对一个类进行一些改动时。改动的不是接口,而是类的实现,即仅仅是一些细节部分,但又一次生成程序时,所实用到这个类的的文件都要又一次编译。

这里题目指的是这个意思。

但实际上,我在vs2012实践了一下,对于类B与类A相关联,类B的实现依赖于类A。若类A的实现发生了改变。并不会影响B。即生成时,编译器仅仅会去又一次编译A。而对于依赖于A的用户程序,并不会像其所说那样所有又一次编译。好吧。我这里总算是明确其所说的改动事实上现的意思了。

改动类的实现: 类的接口仅仅是类中提供给外部的函数, 而类的实现是指类实现接口函数所须要的内部逻辑和数据结构,如一些私有的函数。以及一些私有的成员数据。

改动这些类实现的。对于实现函数的改动就必须改动函数的声明,而数据成员的改动就是数据成员的类型以及数量的改动。当进行这些改动时。就必然会导致调用这个类的用户程序都要又一次编译。

一般的解决方法是将实现与接口分开,即对于本来使用的一个类。将其转换为两个类。一个类为接口类,供用户程序调用,一个类为实现类,有详细的实现逻辑以及实现所需的数据成员。且接口类可以指向相应的实现类,对于实现逻辑的更改不会影响用户程序。由于用户程序仅仅与接口类连接。而隐藏了实现逻辑改动造成的影响,仅仅有当接口改变时。才须要又一次编译。分离的关键是。对类定义的依赖 被 对类声明的依赖代替,减少编译依赖性,将 提供类定义 即#include 指令 的任务由原来的函数声明头文件转交给包括函数调用的用户文件。

即不在头文件里包括其它头文件。除非缺少它们就不能编译,而一个一个地声明所须要的类,让使用这个头文件的用户自己通过include去包括这些头文件。以使用户代码通过编译。

实现这种接口与实现分离,在c++中一般有两种方法,一种是 将一个对象的实现隐藏在指针的背后,即用一个指针指向某个不确定的实现。

这种类称为句柄类或信封类。而指向的实现类称为 主体类或者信件类。句柄类。即接口仅仅是将全部函数调用转移到相应的主体类中,有主题类也就是实现类来真正完毕工作。接口中要将原来的实现须要的数据成员转换为函数,而去调用实现类中的数据成员 来实现功能,即接口中使用函数来实现对实现类中的数据成员实现传递和返回。

假如简单实现两个类,A ,B,C。A中放一个int,b中放一个doubel,C中放两者之和,写出代码例如以下:

classA.h:

#pragma once
class ClassA{
public:
public:
int a;
ClassA(int x){a = x;}
ClassA(){a = 0;}
int getA() const{return a;};
};

ClassB.h:

class ClassB{
public:
double b;
double getB() const{return b;}
ClassB(double x){b = x;}
ClassB(){b = 0;}
};

ClassC.h。即接口:

//这个头文件就是所谓的接口,如此将接口与实现分离后,仅仅有改动接口时,才会导致使用该接口的用户程序又一次编译
class ClassA;//仅仅声明。在接口中仅仅要知道有这些类,而在实现中才去include这些头文件
class ClassB;
class ClassCImpl; class ClassC{
public:
ClassC(const ClassA& xa,const ClassB& xb);
virtual ~ClassC();
int getA() const;//函数来返回实现类中的数据成员
double getB() const;
double getC() const;
private:
ClassCImpl *impl;//使用指针来指向实现类
//int aaa;//在接口中随意进行改动。就要又一次编译其与其用户程序
};

ClassC.cpp,接口的函数,调用 实现类中的函数进行返回。

//这里也是对于接口的实现。改动这里的数据,不会导致其它程序的又一次编译
#include "ClassC.h"//这是接口ClassC的函数详细实现
#include "ClassCImpl.h"//要包括实现类的定义。且实现类中与ClassC中有一样的成员函数 ClassC::ClassC(const ClassA& xa,const ClassB& xb){
impl = new ClassCImpl(xa,xb);
}
ClassC::~ClassC(){
delete impl;
}
int ClassC::getA() const{
return impl->getA();
}
double ClassC::getB() const{
return impl->getB();
}
double ClassC::getC() const{
return impl->getC();
}

ClassCImpl ,实现类的定义:

#include "ClassA.h"
#include "ClassB.h" class ClassCImpl{
public:
ClassCImpl(const ClassA& xa,const ClassB& xb);
int getA() const;//函数实现接口中函数
double getB() const;
double getC() const;
private:
ClassA A;
ClassB B;
ClassB C;
};

ClassCImpl.cpp,实现类的简单的操作:

#include "ClassCImpl.h"//要包括实现类的定义。且实现类中与ClassC中有一样的成员函数

ClassCImpl::ClassCImpl(const ClassA& xa,const ClassB& xb){
A = xa;
B = xb;
C = (B.getB() + A.getA());
}
int ClassCImpl::getA() const{ return A.getA();
}
double ClassCImpl::getB() const{
return B.getB();
}
double ClassCImpl::getC() const{
return C.getB();
}

这样就实现了接口与实现的分离,在ClassC中定义接口,在接口固定的情况下。在接口实现类ClassCImpl中进行随意的改动。编译器都仅仅会又一次编译实现类。而不会所有又一次编译。这是使用句柄类实现的接口与实现分离。



第二种方法成为协议类,即是这个类成为特殊类型的抽象基类。协议类仅仅是为派生类确定接口。它没有数据成员。没有构造函数,有一个虚析构函数,有一些纯虚函数,这些纯虚函数组成了接口。

协议类的用户通过一个类似构造函数的的函数来创建新的对象。而这个构造函数所在的类就是隐藏在后的派生类。

这样的函数一般称为工厂函数,返回一个指针,指向支持协议类接口的派生类的动态分配对象。这个工厂函数与协议类解密相连,所以一般将它声明为协议类的静态成员。若又一次声明一个ClassD。完毕之前的功能,,可是为一个协议类,有:

ClassD.h:

//这个为协议类
class ClassA;//仅仅声明,在接口中仅仅要知道有这些类。而在实现中才去include这些头文件
class ClassB; class ClassD{
public:
virtual ~ClassD(){}
virtual int getA() const = 0;//函数来返回实现类中的数据成员
virtual double getB() const = 0;
virtual double getD() const = 0;
static ClassD* makeClassD(const ClassA& xa,const ClassB& xb);//这里使用静态成员来返回
};

再写一个派生类来实现CLassD的功能,RealClassD.h:

#include "ClassA.h"
#include "ClassB.h"
#include "ClassD.h" class RealClassD:public ClassD{
public:
RealClassD(const ClassA& xa,const ClassB& xb):A(xa),B(xb),D(B.getB() + A.getA()){}
virtual ~RealClassD(){}
int getA() const;
double getB() const ;
double getD() const; private:
ClassA A;
ClassB B;
ClassB D;
};

而在这个派生类定义中,顺带实现ClassD中的返回指针的makeClassD的函数。

这里:先从协议类中继承接口规范,然后在实现中实现接口中的函数。

#include "RealClassD.h"

int RealClassD::getA() const{
return A.getA();
}
double RealClassD::getB() const{
return B.getB();
};
double RealClassD::getD() const{
return D.getB();
}
ClassD* ClassD::makeClassD(const ClassA& xa,const ClassB& xb){
return new RealClassD(xa,xb);
}

而在须要使用的地方,如此调用这个函数来指向须要的接口:

	ClassD* dd = ClassD::makeClassD(a,b);
cout<<dd->getD()<<endl;

dd的指针动态绑定到返回的派生类对象,而在派生类中改动事实上现的成员仅仅要又一次编译派生类的cpp即可了。

句柄类和协议类分离了接口与实现,从而减少了文件间的依赖性。当一定程度上有时间和空间上的消耗。

对于一个程序转变为产品时,要用详细的类来代替句柄类和协议类。

最新文章

  1. 修改.net mvc中前端验证信息的显示方式
  2. Java中的继承
  3. Interview-Increasing Sequence with Length 3.
  4. SqlServer2008 新功能:简单数据加密
  5. SGU 202 The Towers of Hanoi Revisited (DP+递归)
  6. 实验:ignite查询效率探究
  7. 理解inode如何指向block
  8. 扩容 swap 分区
  9. redis编译问题
  10. Android深入四大组件(八)广播的注册、发送和接收过程
  11. 智能指针unique_ptr
  12. 【noip模拟赛7】上网 线性dp
  13. 那些年读过的书《Java并发编程实战》和《Java并发编程的艺术》三、任务执行框架&mdash;Executor框架小结
  14. 字符串问题简述与两个基本问题的Java实现——判断二叉树拓扑结构关系与变形词
  15. mysql定时执行某任务
  16. 【ARM】串行通信
  17. 玩转Bootstrap(JS插件篇)-第1章 模态弹出框 :1-4 模态弹出框--结构分析
  18. POJ 2385
  19. java多台
  20. SpringBoot与Dubbo整合下篇

热门文章

  1. 微信小程序 - 自定义swiper dots样式(非组件)
  2. spring Ioc 实践
  3. SQL Server从BAK文件还原新的数据库
  4. angularjs中的坑
  5. Android 内存泄漏总结(转)
  6. 11、java5线程池之异步任务CompletionService
  7. CentOS7下 简单安装和配置Elasticsearch Kibana Filebeat 快速搭建集群日志收集平台
  8. 理解 LDA 主题模型
  9. weblogic服务目录迁移记录
  10. Oracle死锁导致的tomcat抛损坏的管道异常