条款31:将文件间的关联度降到最低

2024-03-28 11:18

本文主要是介绍条款31:将文件间的关联度降到最低,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

先看一个例子:

class Person
{
public:Person(const string& nm ,Date d):name(nm),birthday(d){}void getBirthday(){cout<<birthday.getYear()<<"."<<birthday.getMonth()<<"."<<birthday.getDay()<<endl;}
private:string name;Date birthday;
};

但是要想使这个类的定义编译通过,那么必须给出它的string 和Date 这两种类型的定义(不是声明)。这意味着,假如Date类里面的东西变了,那么Person也需要重新编译。这使得定义文件和他包含的文件之间形成了一种编译依赖关系。为什么这里不能像函数一样,只需要声明,而不需要定义呢?主要原因是因为编译器在定义一个类的对象时必须知道他的确切大小,要想知道Person的大小,必须知道name和birthday的大小。要想知道他们的的大小,只能通过他们的定义了。


这种问题在Java中并不存在,因为Java中定义一个对象,其实就是拿到了指向这个对象的指针而已,而指针的大小却是固定的。所以就没有上面的问题了。于是,我们可以模仿这个原理,写出:

class Date;
class String;
class PersonImpl;
class Peason
{
public:void getBirthday();
private:std::tr1::shared_ptr<PersonImpl> pImpl;
};

这里这几个类都没有定义,但是Peason类已经可以定义出来了。这说明,Peason类的实现已经于其他几个类完全分离了。至此,如果修改了其他几个类的定义,那么Peason并不需要重新编译。这个分离的本质在于,以声明的存在性替换定义的存在性:即让头文件尽可能自我满足,如果做不到,让他与其他文件内的声明式(而非定义式)相依,由此我们可以得出几个重要的准则:
1.若果使用对象的引用或对象指针可以完成任务,那么就不要使用对象。因为对象的引用和指针时,只用了类的名字,而不需要它的定义,而使用对象时必须要有对象的定义。
2.尽量用类声明替换类定义。比如声明一个函数时,函数的形参,返回值是一个类时,只要求类的声明就好了。但是在调用函数前,必须知道这些类的定义。
3.为声明式和定义事提供不同的头文件。你的程序只需要类的声明就能完成,那么就让他include类声明的头文件。这个思想来源于C++标准库。
下面举一个例子:

//data.h
class Date
{
public:Date(int d, int m, int y):day(d),month(m),year(y){}int getDay(){return day;}int getMonth(){return month;}int getYear(){return year;}Date(const Date& date):day(date.day),month(date.month),year(date.year){}
private:int day;int month;int year;
};//personIplm.h
#include "date.h"class PersonImpl
{
public:PersonImpl(Date d):birthday(d){}Date& getBirthday(){return birthday;}
private:Date birthday;
};//person.h
class Date;class PersonImpl;class Person
{
public:Person(Date d);void getBirthday();
private:std::tr1::shared_ptr<PersonImpl> pImpl;
};//person.cpp
Person::Person(Date d):pImpl(new PersonImpl(d)){}	void Person::getBirthday()
{cout<<pImpl->getBirthday().getYear()<<"."<<pImpl->getBirthday().getMonth()<<"."<<pImpl->getBirthday().getDay()<<endl;
}//main.cpp
int main()
{Person p(Date(12,9,2012));p.getBirthday();return 0;
}

其中Person的定义是不需要其他头文件的,只是在前面声明了其他类就可以了。而Person的函数却实际需要这些这些数据结构,所以它里里含“”了"personIplm.h"和"person.h"。像这种Person中使用pimpl技术的类称为句柄类。
另外一种方法是通过抽象类来实现:

//person.h
class Date;//接口类:抽象基类描述派生类接口
//不带数据成员
class Person
{
public://工厂函数,返回指向这个类的指针static std::tr1::shared_ptr<Person> creat(const Date& d);virtual Date getBirthday() const = 0;};//date.h
class Date
{
public:Date(int d, int m, int y):day(d),month(m),year(y){}int getDay(){return day;}int getMonth(){return month;}int getYear(){return year;}Date(const Date& date):day(date.day),month(date.month),year(date.year){}
private:int day;int month;int year;
};//realperson.h
#include "date.h"
#include "person.h"
//具体的类
class RealPerson:public Person
{
public:RealPerson(const Date& d):birthday(d){}virtual ~RealPerson(){}Date getBirthday()const{return birthday;}
private:Date birthday;
};//person.cpp
#include "realPerson.h"std::tr1::shared_ptr<Person> Person::creat(const Date& d)
{return std::tr1::shared_ptr<Person>(new RealPerson(d));
}

这样以来就解除了接口和实现之间的耦合关系,降低了编译的依存性。


总之,编译依存性最小化的一般构想是依赖于声明,而不依赖于定义。基于此构想的两个手段是:句柄类和接口类。

这篇关于条款31:将文件间的关联度降到最低的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/855462

相关文章

Linux 删除 当前下的 mysql-8.0.31 空文件夹

在Linux中,如果你想要删除当前目录下的名为mysql-8.0.31的空文件夹(即该文件夹内没有任何文件或子文件夹),你可以使用rmdir命令。但是,如果mysql-8.0.31文件夹并非完全为空(即它包含文件或子文件夹),rmdir命令会失败。 如果你的目标是删除mysql-8.0.31文件夹及其内部的所有内容(无论是否为空),你应该使用rm命令结合-r(或-R,它们是等价的)选项来递归地删

App Store最低版本要求汇总

1,自此日期起: 2024 年 4 月 29 日 自 2024 年 4 月 29 日起,上传到 App Store Connect 的 App 必须是使用 Xcode 15 为 iOS 17、iPadOS 17、Apple tvOS 17 或 watchOS 10 构建的 App。将 iOS App 提交至 App Store - Apple Developer 2,最低XCode版本 Xcod

尝试用java spring boot+VUE3实现前后端分离部署(8/31)

前言         这几天开学了,公司这边几个和学校对接的项目都挺忙的,然后我又开始有点闲的情况了。问大佬能不能继续看看若依的项目,大佬让我自己去学了。在看若依的项目的时候在想,python的FLASK后端实现和JAVA spring boot的实现差别大不大,两者实现的思路估计大差不差,那具体的代码逻辑和代码实现又有多大差别,java面向对象的编程思想又是怎么体现的。这些想法迫使我将原来使用

[C/C++入门][进制原理]31、求分数序列和

题目来自于信息学奥赛 1078 分析: 这道题看起来比较复杂,实际上只需要通过两个公式,一次性求出分母和分子,然后把这个求出来的数加入到变量和中。甚至都不需要知道总共游哪些数。数组都用不上。循环就能解决。 #include <iostream>#include <iomanip> // 用于格式化输出using namespace std;int main() {double s

“弹性盒子”一维布局系统(补充)——WEB开发系列31

弹性盒子是一种一维布局方法,用于根据行或列排列元素。元素可以扩展以填补多余的空间,或者缩小以适应较小的空间,为容器中的子元素提供灵活的且一致的布局方式。 一、什么是弹性盒子? CSS 弹性盒子(Flexible Box Layout,简称 Flexbox)是 CSS3 中引入的一种布局模式,提供一种有效的方式来布局、对齐和分配容器内空间,特别是在动态和复杂的应用界面中。 1、

Linux入门攻坚——31、rpc概念及nfs和samba

NFS:Network File System     传统意义上,文件系统在内核中实现 RPC:函数调用(远程主机上的函数),Remote Procedure Call protocol     一部分功能由本地程序完成     另一部分功能由远程主机上的 NFS本质上是一种RPC的实现。 本地用户进程要使用文件系统,通过系统调用,由内核完成文件系统的操作,而NFS只不过是系统内核又通过RP

LeetCode 31 Next Permutation

题意: 给出一串数字,求该排列的下一个排列。如果该排列为字典序最大排列,则输出字典序最小排列。 思路: 首先C++里面有求下一个排列的函数next_permutation,该函数返回0即表示当前排列字典序最大。 如果要自己动手实现,那么就考虑“如何找到比当前排列大1的排列”。 假设排列A(aaaXddd)比排列B(aaaYfff)大1,a部分为两排列相同部分,X与Y表示最靠左边不同

C++相关概念和易错语法(31)(特殊类的设计、new和delete底层调用分析)

特殊类的设计 在实践过程中,我们难免会接触到一些需要实现特定功能的类。像之前提过的unique_ptr就是直接delete拷贝构造和赋值函数。下面会分享一些常见的特殊类的实现 1、防拷贝和防赋值 通过封死拷贝构造和赋值函数来保护对象里面内容不被复制。如果对象里面的内容是指针,对析构次数有严格要求的话(如unique_ptr)就通常采用这种处理方法。 注意拷贝构造和移动拷贝为一体,赋值重

快递员送货最短路径和最低费用

一、问题定义 1、[快递公司送货策略](https://www.docin.com/p-700721704.html) 来自数学建模训练题目,解决办法“多目标动态规划” 二、相关论文 1.[A Model and Algorithm for the Courier Delivery Problem with Uncertainty(https://www.researchgate.net/

Python精选200Tips:31-40

With dreams ahead, I fear no storms 031 any032 all033 try034 del035 not036 return037 with038 yield039 next040 from 运行系统:macOS Sonoma 14.6.1 Python编译器:PyCharm 2024.1.4 (Community Edition) Pytho