chapter 4 设计与声明
item23 :宁以non-member、non-friend函数替换member函数
1)假设这里有一个关于web浏览器的一个类,它有一系列的清楚操作,最后需要写一个函数来清除所有的东西,也就是说调用它所有的清楚操作。代码如下:
class WebBrowser {
public:
...
void clearCache();
void clearHistory();
void removeCookies();
...
};
//方法1:以成员函数的方式实现这个功能
class WebBrowser {
public:
...
void clearEverything(); // calls clearCache, clearHistory,and removeCookies
...
};
//方法2:以非成员函数的方式来实现这个功能
void clearBrowser(WebBrowser& wb)
{
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}
当然,上面两种实现方法,哪一个更好。可能从面向对象原理来说,不是要求封装么?那肯定是第一个方法更好了。如果你这样想,那就是对面向对象机制的一个误解了。下面从两个方面来论述这个。首先,封装这个词的含义就是把什么东西隐藏起来,一个东西封装得越好,就应该是越少的人能够看到。越少的人看得到,那就越方便维护。因为如果需要做出修改的时候,只需要修改可以看到的东西就行了。其次考虑一个对象的成员变量。item22中已经说了,成员变量尽量写成private,方便日后修改,而修改一个private成员变量的工作量就取决于用到过它的成员函数和友元函数的数量,因为只有这些函数可以访问private成员变量。所以如果一个成员函数和一个非成员函数对比起来,非成员函数能提供更好的封装性。
2)在这一点上,有两件事值得注意。首先,因为友元函数的访问权和成员函数一样,都可以之间访问private变量。所以这里不是在成员函数和非成员函数之间做选择(当然封装并非唯一考虑,item24会解释当需要有隐式转换的时候,就需要在成员函数和非成员函数之间做出选择),而是成员函数和非成员非友元函数之间做选择。其次,从封装性的角度来看,一个函数不能成为一个类的成员函数,不代表它不能成为另一个类的成员函数。我们可以把clearBrowser作为另一个实用类的静态成员函数,只要它不是WebBrowser的一部分,就不会破坏WebBrowser成员变量的封装性。
3)在C++中,一个更自然的方式就是把某一个类的非成员函数和这个类放在同一个namespace之中。当然这不仅仅是因为看起来自然那么简单。namespace可以横跨多个源文件,但是class不能。这个很重要,像clearBrowser这种convenience function,既不是成员函数也不是友元函数,没有特殊的访问权利,也不能提供"用户以其他方式获得"的机能,比如说clearBrowser不存在,用户就只能自己调用一系列的清除函数。
namespace WebBrowserStuff {
class WebBrowser { ... };
void clearBrowser(WebBrowser& wb);
...
}
4)当然一个类可能有大量的convenience function,比如WebBrowser,有些与书签有关,有些与打印有关,还有些与cookie管理有关。没理由说一个用户如果只关心跟书签有关的convenience function,却和cookie管理有关的convenience function发生编译相依的关系。分离他们的最后途径就是把不同相关的convenience function放在不同的同文件中,如:
// header "webbrowser.h" — header for class WebBrowser itself
// as well as "core" WebBrowser-related functionality
namespace WebBrowserStuff {
class WebBrowser { ... };
... // "core" related functionality, e.g.non-member functions almost all clients need
}
// header "webbrowserbookmarks.h"
namespace WebBrowserStuff {
... // bookmark-related convenience functions
}
// header "webbrowsercookies.h"
namespace WebBrowserStuff {
... // cookie-related convenience functions
}
注意到这其实就是C++标准库的组织方式。比如说namespace std,里面就有数十个头文件,每个头文件实现某些功能,需要用哪一个,就include哪一个就可以了。这样允许客户只对他所用的那一小部分系统形成编译相依(item31中讨论了降低编译依存性的其他做法)。以这种方式来切分class是不可能的,因为class必须作为一个整体来定义。
5)把所有的convenience function放在同一namespace但是不同的头文件中,也意味着用户可以很方便的扩展convenience function。比如说添加一个与下载有关的convenience function,只需要在WebBrowserStuff的namespace中新建一个头文件,声明需要添加的convenience function。那这些函数就可用了。这是class不可能做到的,因为类对于客户而言是不可扩展的,当然它可以派生出新的类,但是派生类没办法访问到基类的private变量。另外,不是每个类都设计作为基类的。
6)所以用non-member、non-friend函数,可以增加封装性(encapsulation),包裹弹性(packaging flexibility)和技能扩充性(functional extensibility)。