Skip to content

Latest commit

 

History

History
82 lines (63 loc) · 3.37 KB

File metadata and controls

82 lines (63 loc) · 3.37 KB

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

想象有个class用来表示网页浏览器。这样的class可能提供的众多函数中,有一些用来清除下载元素的高速缓存区、清除访问过的URL的历史记录、以及移除系统中的所有cookies:

class WebBrowser {
public:
  ...
  void clearCache();
  void clearHistory();
  void removeCookies();
  ...
};

许多用户会想一起执行这些操作,因此WebBrowser也提供这样一个函数:

class WebBrowser {
public:
  ...
  void clearEverything(); // 调用clearCache, clearHistory, removeCookies
  ...
};

当然,这一功能也可由一个non-member函数调用适当的member函数而提供出来:

void clearBrowser(WebBrowser& wb) {
  wb.clearCache();
  wb.clearHistory();
  wb.removeCookies();
}

那么,哪一个比较好呢?

面向对象守则要求,数据以及操作数据的那些函数应该被捆绑在一块,这意味着它建议member函数是较好的选择。然而,这是基于面向对象真实意义的一个误解。面向对象守则要求数据应该尽可能被封装,然而与直觉相反地,member函数clearEverything带来的封装性比non-member函数clearBrowser低。此外,提供non-member函数可允许对WebBrowser相关功能由较大的包装弹性,增加WebBrowser的可延展性。因此在许多方面non-member做法比member做法好。

越少代码可以看到数据,越多数据可以被封装,我们也就能越自由地改变对象数据。我们计算能够访问该数据地函数数量,作为一种粗糙的测量。越多函数可以访问它,数据的封装性就越低。

如果你要在一个member函数和一个non-member、non-friend函数之间做抉择,并且两者提供相同的功能,那么导致较大封装性的是non-member、non-friend函数,因为它并不增加能够访问class内private成分的函数数量。这也解释了为什么clearBrowser比clearEverything更受欢迎的原因:它导致WebBrowser有较大的封装性。

C++中,比较自然的做法是让clearBrowser成为一个non-member函数并且位于WebBrowser所在的同一个namespace中:

namespace WwbBrowserStuff {
  class WebBrowser {...};
  void clearBrowser(WebBrowser& wb);
  ...
}

一个像WebBrowser这样的class可能拥有大量便利函数,某些与书签有关,某些与打印有关,还有一些与cookie的管理有关……通常大多数客户只对其中的部分功能感兴趣。分离便利函数最直接的做法就是将不同功能相关声明声明于不同的头文件:

// file: webbrowser.h
namespace WebBrowserStuff{
class WebBrowser {...};
...   // 核心功能
}

// file: webbrowserbookmarks.h
namespace WebBrowserStuff{ 
  ... // 与书签相关的便利函数
}

// file: webbrowsercookies.h
namespace WebBrowserStuff{
  ... // 与cookie相关的便利函数
}
...

这正是C++标准库的组织方式。每个头文件声明std命名空间的某些功能,仅把需要的功能包含进程序。

将所有便利函数放在多个头文件但隶属同一个命名空间,意味着客户可以轻松拓展这一组便利函数。它们需要做的就是添加更多non-member、non-friend函数到此命名空间中。

请记住

  • 宁可拿non-member、non-friend替换member函数。这样做可以增加封装性、包装弹性和功能扩充性。