条款23:宁以non-member、non-friend替换member函数

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必须整体定义,不能被分割成片段。

原文地址:https://www.cnblogs.com/lasnitch/p/12764172.html