Effective C++ 34
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即可了。
句柄类和协议类分离了接口与实现,从而减少了文件间的依赖性。当一定程度上有时间和空间上的消耗。
对于一个程序转变为产品时,要用详细的类来代替句柄类和协议类。
Effective C++ 34的更多相关文章
- Effective Java 34 Emulate extensible enums with interfaces
Advantage Disadvantage Enum types Clarity Safety Ease of maintenance. None extensibility Typesafe en ...
- Effective C++ 34 区分接口继承和实现继承
public继承从根本上讲,有两部分:接口继承和实现继承.两者之前的区别很像函数声明与函数定义. 具体设计中,会呈现三种形式:derived class只继承成员函数的接口(纯虚函数):derived ...
- Effective Java Index
Hi guys, I am happy to tell you that I am moving to the open source world. And Java is the 1st langu ...
- Effective Java 第三版——34. 使用枚举类型替代整型常量
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective STL 学习笔记 Item 34: 了解哪些算法希望输入有序数据
Effective STL 学习笔记 Item 34: 了解哪些算法希望输入有序数据 */--> div.org-src-container { font-size: 85%; font-fam ...
- Effective C++ -----条款34:区分接口继承和实现继承
接口继承和实现继承不同.在public继承之下,derived classes总是继承base class的接口. pure virtual函数只具体指定接口继承. 简朴的(非纯)impure vir ...
- [Effective JavaScript 笔记]第34条:在原型中存储方法
js中完全有可能不借助原型进行编程.不用在其原型中定义任何的方法. 创建对象 构造函数法 所有属性和方法都在构造函数中定义 function User(name,pwd){ this.name=nam ...
- Effective C++:规定34:区分接口继承和实现继承
(一个) class Shape { public: virtual void draw() const = 0; virtual void error(const string& msg); ...
- 读书笔记 effective c++ Item 34 区分接口继承和实现继承
看上去最为简单的(public)继承的概念由两个单独部分组成:函数接口的继承和函数模板继承.这两种继承之间的区别同本书介绍部分讨论的函数声明和函数定义之间的区别完全对应. 1. 类函数的三种实现 作为 ...
随机推荐
- LCD显示——点阵字体
Bitmap font 点阵字体是把每一个字符都分成16×16或24×24个点,然后用每个点的虚实来表示字符的轮廓. 点阵字体优点是显示速度快,不像矢量字体需要计算:其最大的缺点是不能放大,一旦放大后 ...
- iOS开发,更改状态栏(StatusBar)文字颜色为白色
详细实现步骤 1.如图在Info.plist中进行设置,主要用于处理启动画面中状态栏(StatusBar)文字颜色. watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5u ...
- java.net.ConnectException: Connection refused: no further information
NIO项目中出现了这个错误: java.net.ConnectException: Connection refused: no further information 一般是因为InetSocket ...
- 【Zookeeper】源码分析之持久化(三)之FileTxnSnapLog
一.前言 前面分析了FileSnap,接着继续分析FileTxnSnapLog源码,其封装了TxnLog和SnapShot,其在持久化过程中是一个帮助类. 二.FileTxnSnapLog源码分析 2 ...
- 使用 axios 详解
Vue.js 1.0 我们常使用 vue-resource (官方ajax库), Vue 2.0 发布后作者宣告不再对 vue-resource 进行更新, 推荐我们使用 axios (基于 Prom ...
- Orabbix监控Oracle 11g
Orabbix简介说明 orabbix是一个用来监控oracle数据库性能的zabbix插件工具,通过安装在被监控服务器上客户端上收集数据并传给zabbix服务器端,然后通过调用图形显示.具有以下功能 ...
- java 日期工具类DateUtils
日期工具类DateUtils CreateTime--2017年5月27日08:48:00Author:Marydon DateUtils.java-对日期类的进一步封装 import java. ...
- C#正则表达式匹配HTML中的图片路径
最近的项目中有个关于网页取图的功能需要我自己开发,那就是用正则表达式来匹配图片标签. 一般来说一个 HTML 文档有很多标签,比如“<html>”.“<body>”.“< ...
- txt文件匹配脚本
# -*- coding:utf-8 -*- import time start = time.clock() data=open("Data.txt","r" ...
- 长按listview弹出选项列表对话框
Android中通过xml资源文件定义数组.来自微风的网络日志. 文章链接:http://leybreeze.com/blog/?p=1524 Android ListView两种长按弹出菜单方式 h ...