封装和抽象是C++里面的重要概念,本文试着对此作一下简单的讨论。
封装是OO的重要特征,这一点是毫无疑问的,至于算不算的上是OO的三大特征之一(另外两个是继承、多态),目前仍有争论,我也不想在这个问题上作过多的纠缠。
什么是封装呢?封装是人们对现实世界中解决问题时,为了进行简化问题,对研究的对象所采用的一种方法,一种信息屏蔽技术。例如:打电话的时候,我们只需简单的按几个按钮就可以了,我们没有必要去了解电话内部的线路、网络的连接,这样一来通过对实现细节的封装,就使得本身很复杂的问题变得非常简单,容易操作,并且出错的几率大大减小。
封装的主要目的就是达到接口和实现的分离。通过封装,对内我们将实现细节隐藏起来,对外我们则通过接口向客户提供相应的服务。将接口和实现分离是避免所谓的“涟漪现象”的关键(涟漪现象,即“一石激起千层浪”。在软件开发过程中,经常会出现为了某个目的,修改一个文件A,结果必须修改与之相关的其他文件B、C,修改文件B、C就要修改相关的文件D、E、F、G,如此循环往复,就会造成大规模的修改,最终会以瀑布式扩散到系统的各个地方。就象在湖中央投下了一颗石子,造成整个湖面的波动,“涟漪现象”由此而得名)。
将接口和实现分离是软件重复使用的一个手段,其作用主要体现在以下4个方面:
1、降低了系统内部各个模块之间的耦合,使系统更有弹性。
2、将软件中的BUG精确定位到较小的模块,有助于软件的调试工作。
3、降低系统的复杂性,使客户更易理解和使用。
4、分离造成的简单性,更有助于软件的重复使用。
抽象是对象的最简化的接口,他向客户提供了所期望的服务。理解抽象的关键,在于接口,抽象是对象接口的抽象,对于特定的对象,抽象就是一个明确的接口,对于电话,按钮就是他的接口(抽象)。
用好抽象的关键在于对所研究问题的深刻理解。设计的比较好的抽象可以把说明从实现中分离出来,通过提供必备的信息,隐藏起实现的细节,让客户以较为安全和客预测的方式使用对象,同时通过降低复杂性,以简单接口的方式降低学习难度,提高学习效率。
好的抽象,应该使客户打消“偷窥”的念头(不过总是有些人想方设法去窥视具体的实现),按照抽象编程,使得最终的客户代码更加的简单、安全和稳定,在这方面,标准程序库和各种准标准程序库做得最为出色。
一个好的抽象必须提供明确的、完整的、易于理解的说明,依赖抽象编程,实际上就是依赖这些说明进行编程,他比依赖实现编程有很大的优势:
1、阅读说明比阅读源代码容易得多,时间节省了很多,也更容易理解。
2、通过说明的完整性(例如先置条件、后置条件、适用范围等),我们可以很清楚的了解一个类的完整性。
3、通过说明,我们的接口具有更大的弹性和可扩展性(例如我们可以采用更加有效的方法实现sort接口)。
4、规定接口必须有说明,把那些没有相应说明的接口定义为错误,使得类的维护更加容易。
一个好的说明应该是规定接口必须提供的服务,必须完成的任务,而不是已经做过的事情。
封装与抽象是密不可分的。
封装通过提供抽象,隐藏了实现的细节,通知客户那些是可以随便使用的稳定的服务,那些是随着版本的更新有可能发生变动的细节,例如有一个接口:void sort();通过接口的说明,客户可以知道这个接口用来进行排序,具体的排序方法是什么,客户无需关心,可能在版本1中,采用的是冒泡排序,在升级的版本2中,采用的则是快速排序。客户既然不用关心细节,那么使用什么方法对客户的代码不会带来任何影响。对于一个已经公布的接口/抽象,我们不能够随意更改,例如在版本1中提供了sort接口,那么在升级的版本2中必然也要有这个接口,因为版本公布之后,我们就无法知道,这个接口是不是有人在使用,如果我们在版本2中禁止了sort接口,那么所以使用版本1的客户代码无法升级,若强行升级,势必带来客户代码的中断,这个特点在COM中尤为明显。
封装是手段,抽象是目的。