声明:
Prefer non-member non-friendfunctions to member functions.
本条款讨论的还是封装性的问题。
假设有一个class用来表示网页浏览器,代码如下:
class WebBrowser
{
public:
...
void clearCache(); //清除下载缓存
void clearHistory(); //清除URLs历史记录
void removeCookies(); //清除系统中的所有cookies
...
};
有的用户可能会觉得使用一个操作来执行这些任务比较方便,因此WebBrowser也提供这样一个成员函数:
class WebBrowser {
public:
...
void clearEverything(); //调用上述的三个函数
...
};
当然,这一机能也可以由一个non-memebr函数调用适当的member函数而提供:
void clearBrowser (WebBrowser& wb)
{
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}
那么,是member函数clearEverything好还是non-member函数clearBrowser好呢?
根据面向对象守则要求,数据及操作数据的那些函数应该被捆绑在一块。这意味着member函数是较好的选择。然而事实上并非如此!
面向对象守则要求数据应该尽可能地被封装,然而与直观相反地,member函数clearEverything所带来的封装性比non-member函数clearBrowser要低。此外,提供non-member函数可允许对WebBrowser相关机能有较大的包裹弹性(packaging flexibility),而那最终导致较低的编译相依度,增加WebBrowser的可延伸性。因此,许多方面non-member做法比member做法好。下面讨论一下原因。
首先从封装开始讨论。
如果某些东西被封装,它就不再可见。越多的东西被封装,就越少的人可以看见。而越少的人可以看到它,我们就有越大的弹性去改变它,因为我们的改变仅仅直接影响看到改变的那些人事物。因此,越多的东西被封装,我们改变这些东西的能力就越大。这就是我们首先推崇封装的原因:封装使得我们能够改变事物而只影响有限的客户。
其次我们考虑对象内的数据。
越少的代码可以看到数据(即访问到它),越多的数据就可以封装,而我们就越能自由地改变对象数据,例如改变成员变量的数量、类型等。越多的函数可以访问类的成员数据,它的封装性就越低。
条款22曾经说过,成员变量应该是private,能够访问private成员变量的函数只有class的member函数以及friend函数。member函数,它可以访问class内的:private数据、private函数、enums、typedefs等等。而对于non-member函数,它无法访问上述任何东西。
如果我们需要在member函数和non-member函数中选择,而且两者提供相同的机能,则:导致较大封装性的是non-member non-friend函数,因为它并不会增加“能够访问class内的private”的函数数量。
这也就解释了为什么clearBrowser(non-member non-friend函数)比clearEverything(member函数)更受欢迎: 它导致WebBrowser class有有较大的封装性。
有一点要注意的是:该non-member函数可以定义在其他类中,不影响原类private成员的封装性。在C++中,比较自然的做法是:让clearBrowser成为一个non-member函数,并且位于WebBrowser所在的同一个namespace(命名空间)中,eg:
namespace WebBrowserStuff
{
class WebBrowser{...};
void clearBrowser(WebBrowser &wb);
...
}
namespace与class不同,前者可以跨越多个源码文件,而后者不能。
另外,一个像WebBrower这样的class可能拥有大量便利函数以实现相关功能,比如书签、打印、cookie管理等等。一个较好的做法是将书签、打印、cookies管理等函数声明于不同的头文件,如下:
// 头文件webbrowser.h,仅针对class WebBrowser自身
namespace WebBrowserStuff
{
class WebBrowser{...};
void ClearWebBrowser(WebBrowser& w); //核心机能,几乎所有客户都需要的non-member non-friend函数
...
}
// 头文件webbrowserbookmarks.h
namespace WebBrowserStufff
{
...// 与书签相关的函数
}
// 头文件 webbrowsercookies.h
namespace WebBrowserStuff
{
...// 与cookie管理相关的函数
}
这正是C++标准程序库的组织方式——将所有便利函数放在多个头文件内但隶属头一个命名空间。标准程序库并不是拥有单一、整体、庞大的头文件并在其中内含std命名空间的每一样东西,而是有数十个头文件(
请记住:宁可拿non-member、non-friend函数替换member函数,这样做可以增加封装性、包裹弹性和机能扩充性。