想象有個(gè)class用來(lái)表示網(wǎng)頁(yè)瀏覽器。這樣的class可能提供的眾多函數(shù)中,有一些用來(lái)清除下載元素高速緩存區(qū)(cache of downloaded elements)、清除訪問(wèn)過(guò)的URLs的歷史記錄(history of visited URLs)、以及移除系統(tǒng)中的所有cookies:
class WebBrowser
{
public:
// ...
void clearCache();
void clearHistory();
void removeCookies();
// ...
};
許多用戶(hù)會(huì)想一整個(gè)執(zhí)行所有這些動(dòng)作,因此WebBrowser也提供這樣一個(gè)函數(shù):
class WebBrowser
{
public:
// ...
void clearEverything(); // 調(diào)用clearCache、clearHistory、removeCookies
// ...
};
當(dāng)然,這一機(jī)能也可由一個(gè)non-member函數(shù)調(diào)用適當(dāng)?shù)膍ember函數(shù)而提供出來(lái):
void clearBrowser(WebBrowser &wb)
{
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}
那么,哪一個(gè)比較好呢?是member函數(shù)clearEverything還是non-member函數(shù)clearBrowser?
面向?qū)ο笫貏t要求,數(shù)據(jù)以及操作數(shù)據(jù)的那些函數(shù)應(yīng)該被捆綁在一塊,這意味它建議member函數(shù)是較好的選擇。不幸的是這個(gè)建議不正確。這是基于對(duì)面向?qū)ο笳鎸?shí)意義的一個(gè)誤解。面向?qū)ο笫貏t要求數(shù)據(jù)應(yīng)該盡可能被封裝,然而與直觀相反地,member函數(shù)clearEverything帶來(lái)的封裝性比non-member函數(shù)clearBrowser低。此外,提供non-member函數(shù)可允許對(duì)WebBrowser相關(guān)機(jī)能有較大的包裹彈性(packaging flexibility),而那最終導(dǎo)致較低的編譯相依度,增加WebBrowser的可延伸性。因此在許多方面non-member做法比member做法好。重要的是,我們必須了解其原因。
讓我們從封裝開(kāi)始討論。如果某些東西被封裝,它就不再可見(jiàn)。愈多東西被封裝,愈少人可以看到它。而愈少人看到它,我們就有愈大的彈性去變化它,因?yàn)槲覀兊母淖儍H僅直接影響看到改變的那些人事物。因此,愈多東西被封裝,我們改變那些東西的能力也就愈大。這就是我們首先推崇封裝的原因:它使我們能夠改變事物而只影響有限客戶(hù)。
現(xiàn)在考慮對(duì)象內(nèi)的數(shù)據(jù)。愈少代碼可以看到數(shù)據(jù)(也就是訪問(wèn)它),愈多的數(shù)據(jù)可被封裝,而我們也就愈能自由地改變對(duì)象數(shù)據(jù),例如改變成員變量的數(shù)量、類(lèi)型等等。如何量測(cè)“有多少代碼可以看到某一塊數(shù)據(jù)”呢?我們計(jì)算能夠訪問(wèn)該數(shù)據(jù)的函數(shù)數(shù)量,作為一種粗糙的量測(cè)。愈多函數(shù)可訪問(wèn)它,數(shù)據(jù)的封裝性就愈低。
條款22曾說(shuō)過(guò),成員變量應(yīng)該是private,因?yàn)槿绻鼈儾皇?,就有無(wú)限量的函數(shù)可以訪問(wèn)它們,它們也就毫無(wú)封裝性。能夠訪問(wèn)private成員變量的函數(shù)只有class的member函數(shù)加上friend函數(shù)而已。如果你要在一個(gè)member函數(shù)(它不只可以訪問(wèn)class內(nèi)的private數(shù)據(jù),也可以取用private函數(shù)、enums、typedefs等等)和一個(gè)non-member non-friend函數(shù)(它無(wú)法訪問(wèn)上述任何東西)之間做抉擇,而且兩者提供相同機(jī)能,那么,導(dǎo)致較大封裝性的是non-member non-friend函數(shù),因?yàn)樗⒉辉黾印澳軌蛟L問(wèn)class內(nèi)之private成分”的函數(shù)數(shù)量。這就解釋了為什么clearBrowser(一個(gè)non-member non-friend函數(shù))比clearEverything(一個(gè)member函數(shù))更受歡迎的原因:它導(dǎo)致WebBrowser class有較大的封裝性。
在這一點(diǎn)上有兩件事情值得注意。第一,這個(gè)論述只適用于non-member non-friend函數(shù)。friend函數(shù)對(duì)class private成員的訪問(wèn)權(quán)力和member函數(shù)相同,因此兩者對(duì)封裝的沖擊力道也相同。從封裝的角度看,這里的選擇關(guān)鍵并不在member和non-member函數(shù)之間,而是在member和non-member non-friend函數(shù)之間(當(dāng)然,封裝并非唯一考慮,條款24解釋當(dāng)我們考慮隱式類(lèi)型轉(zhuǎn)換,應(yīng)該在member和non-member函數(shù)之間抉擇)。
第二件值得注意的事情是,只因在意封裝性而讓函數(shù)“成為class的non-member”,并不意味著它“不可以是另一個(gè)class的member”。這對(duì)那些習(xí)慣于“所有函數(shù)都必須定義于class內(nèi)”的語(yǔ)言(如Eiffel、Java、C#)的程序員而言,可能是個(gè)溫暖的慰藉。例如我們可以令clearBrowser成為某工具類(lèi)(utility class)的一個(gè)static member函數(shù)。只要它不是WebBrowser的一部分(或成為其friend),就不會(huì)影響WebBrowser的private成員封裝性。
在C++,比較自然的做法是讓clearBrowser成為一個(gè)non-member函數(shù)并且位于WebBrowser所在的同一個(gè)namespace(命名空間)內(nèi):
namespace WebBrowserStuff
{
class WebBroser
{
// ...
};
void clearBrowser(WebBrowser &wb);
// ...
}
然而這不只是為了看起來(lái)自然而已。要知道,namespace和class不同,前者可以跨越多個(gè)源碼文件而后者不能。這很重要,因?yàn)橄馽learBrowser這樣的函數(shù)是個(gè)“提供便利的函數(shù)”,如果它既不是member也不是friend,就沒(méi)有對(duì)WebBrowser的特殊訪問(wèn)權(quán)力,也就只能提供“WebBrowser客戶(hù)以其他方式也能取得”的機(jī)能。舉個(gè)例子,如果clearBrowser不存在,客戶(hù)端就只好自行調(diào)用clearCache、clearHistory、removeCookies。
一個(gè)像WebBrowser這樣的class可能擁有大量便利函數(shù),某些與書(shū)簽(bookmarks)有關(guān),某些與打印有關(guān),還有一些與cookies的管理有關(guān)……通常大多數(shù)客戶(hù)只對(duì)其中某些感興趣。沒(méi)道理一個(gè)只對(duì)書(shū)簽相關(guān)便利函數(shù)感興趣的客戶(hù)卻與例如一個(gè)cookie相關(guān)便利函數(shù)發(fā)生編譯相依關(guān)系。分離它們的最直接做法就是將書(shū)簽相關(guān)便利函數(shù)聲明于一個(gè)頭文件,將cookie相關(guān)便利函數(shù)聲明于另一個(gè)頭文件,再將打印相關(guān)便利函數(shù)聲明于第三個(gè)頭文件,依此類(lèi)推:
// 頭文件“webbrowser.h”——這個(gè)頭文件針對(duì)class WebBrowser自身及WebBrowser核心機(jī)能
namespace WebBrowserStuff
{
class WebBrowser
{
// ...
};
// ... 核心機(jī)能,例如幾乎所有客戶(hù)都需要的non-member函數(shù)
}
// 頭文件“webbrowserbookmarks.h”
namespace WebBrowserStuff
{
// ... 與書(shū)簽相關(guān)的便利函數(shù)
}
// 頭文件“webbrowsercookies.h”
namespace WebBrowserStuff
{
// ... 與cookie相關(guān)的便利函數(shù)
}
// ...
注意,這正是C++標(biāo)準(zhǔn)庫(kù)的組織方式。標(biāo)準(zhǔn)程序庫(kù)并不是擁有單一、整體、龐大的<C++StankardLibrary>頭文件并在其中內(nèi)含std命名空間內(nèi)的每一樣?xùn)|西,而是有數(shù)十個(gè)頭文件(<vector>
、<algorithm>
、<memory>
等等),每個(gè)頭文件聲明std的某些機(jī)能。如果客戶(hù)只想使用vector相關(guān)機(jī)能,他不需要#include <memory>
;如果客戶(hù)不想使用list,也不需要#include <list>
。這允許客戶(hù)只對(duì)他們所用的那一小部分系統(tǒng)形成編譯相依(見(jiàn)條款13,其中討論降低編譯依存性的其他做法)。以此種方式切割機(jī)能并不適用于class成員函數(shù),因?yàn)橐粋€(gè)class必須整體定義,不能被分割為片片段段。
將所有便利函數(shù)放在多個(gè)頭文件內(nèi)但隸屬同一個(gè)命名空間,意味客戶(hù)可以輕松擴(kuò)展這一組便利函數(shù)。他們需要做的就是添加更多non-member non-friend函數(shù)到此命名空間內(nèi)。舉個(gè)例子,如果某個(gè)WebBrowser客戶(hù)決定寫(xiě)些與影像下載相關(guān)的便利函數(shù),他只需要在WebBrowserStuff命名空間內(nèi)建立一個(gè)頭文件,內(nèi)含那些函數(shù)的聲明即可。新函數(shù)就像其他舊有的便利函數(shù)那樣可用且整合為一體。這是class無(wú)法提供的另一個(gè)性質(zhì),因?yàn)閏lass定義式對(duì)客戶(hù)而言是不能擴(kuò)展的。當(dāng)然,客戶(hù)可以派生出新class,但derived class無(wú)法訪問(wèn)base class中被封裝的(即private)成員,于是如此的“擴(kuò)展機(jī)能”擁有的只是次級(jí)身份。此外一如條款7所說(shuō),并非所有class都被設(shè)計(jì)用來(lái)作為base class。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-840006.html
請(qǐng)記住:
寧可拿non-member non-friend函數(shù)替換member函數(shù)。這樣做可以增加封裝性、包裹彈性和機(jī)能擴(kuò)充性。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-840006.html
到了這里,關(guān)于Effective C++ 學(xué)習(xí)筆記 條款23 寧以non-member、non-friend替換member函數(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!