1、问题引出
(1)假设有一个WebBrower类,代表浏览器,其中有的成员方法有:
- 清除缓存
- 清除url
- 清除cookies
class WebBrower
{
public:
void ClearCach();
void ClearHistory();
void RemoveCookies();
};
(2)现在有一个需求:把这三个同时同时清除。但是我们实现上述需求有两种方式:
方式一:在WebBrower类中再写一个成员方法,直接在其内部调用其他成员方法。
class WebBrower
{
…
void ClearEverything()
{
ClearCach();
ClearHistory();
RemoveCookies();
}
…
};
方式二:写一个不属于这个类的函数,在函数内调用这个类的三个成员方法。
void ClearWebBrowser(WebBrower& w)
{
w.ClearCach();
w.ClearHistory();
w.RemoveCookies();
}
现在的问题是,哪一种实现方式更好?也就是把它写成成员函数好,还是写成 non-member 、non-friend 函数好呢?
答案是 写成:non-member、non-friend 好。
2、为什么写成non-member、non-friend 好?
首先,对于面向对象的一个误解:数据应该和操作数据的函数绑定在一起。如果按照这种解释,那么应该写成member的。但是实际上,面向对象强调的是封装性。
- 面向对象真正强调的是封装性,对于上述问题,non-member、non-friend函数的封装性要比member函数好。
- non-member函数允许对浏览器类有较大的包裹弹性,较大的包裹弹性,将导致较低的编译相依度,增加浏览器类的可延展性。
3、关于上述两个原因的进一步解释一:封装性
(1)为什么强调封装性?
所谓封装就是不可见,越多东西被封装,能够看见它的人越少。越少的人看到它,我们就能够更大弹性的修改它。因此,封装性越好,我们改变实现的能力就越高。推崇封装的原因:我们能够改变事物,而只影响有限的客户。
(2)如何衡量封装性?
我们计算能够访问该数据的成员函数以及其它函数的数量,作为一种粗糙的衡量。越多的函数能够访问它,它的封装性就越低。
例如:public数据,所有的函数都可以访问它,它就是毫无封装性的。private数据,只有friend和member函数可以访问它,它的封装性的高低,就和能够访问它的friend函数和member函数数量有关,数量越大,代表封装性越低,数量越小,代表封装性越高。
总之,在实现同一机能的情况下,面对使用member函数和non-member、non-friend函数的抉择时,后者提供更好的封装性。
这就从封装性的角度解释了上述例子。
(3)注意
关于上述论断,有两个需要注意的点。
第一: non-member、friend函数 和member函数时一样的,都可以访问私有数据。因此只是non-member、non-friend 的函数和member函数之间存在封装性程度的高低不一。
第二:可以将函数写成另一个类类的member函数。例如,可以使得ClearWebBrowser() 函数称为另外一个工具类的static member函数。non-member指的是不能将其写成浏览器类WebBrower 的成员函数。(这对于其它语言的程序员来说一个温暖的慰藉,因为有些语言只能将函数写到class内部。)
4、关于上述两个原因的进一步解释二:编译相依度、包裹弹性
虽然可以将其写到其它类中,但是C++比较自然的做法是,将它写成一个non-member函数,并让它和浏览器类在同一个命名空间中。
这样的做法是由原因的:
(1)原因一:可以降低文件间的编译相依度。
namespace 和class 是不同的,namespace是可以跨越多个文件的,但是class却不能。class 内的是核心技能,但是便利函数只是提供便利的,可有可无的。即使没有便利函数,用户可以通过访问class进行相关的操作。因此说便利函数时外覆的。
一个类可以由不同的机能分化出拥有多个便利函数,与cookies管理有关的、与书签有关的、与打印有关的。用户可能只对其中一部分感兴趣,那就没有必须让他们之间存在编译相依的关系。可以将他们进行分离,与不同模块相关的便利函数写到不同的头文件中,这样用户对哪个模块感兴趣,就包含哪个头文件就可以了。
(2)原因二:可以轻松的扩展像这样的便利函数。(体现包裹弹性)
将所有便利函数放在多个头文件中但隶属于同一个命名空间,意味着客户可以轻松扩展这一组便利函数。他们需要增加什么便利函数时,也添加到这个命名空间就好。class就不能这样扩展。
注意: 原因一种组织代码的 方式也正是C++标准程序库的组织方式。C++并没有将所有的功能都写到一个头文件里,而是写成数十个头文件,每个头文件中包含某些机能。用户需要什么,就包含什么。这样形成一个编译相依的小系统。 这种分割机能的方式不适用于class成员函数,因为class必须整体定义,不能被分割成片段。