日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

C++继承和组合——带你读懂接口和mixin,实现多功能自由组合

發(fā)布時(shí)間:2024/8/23 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++继承和组合——带你读懂接口和mixin,实现多功能自由组合 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

摘要:?本文詳細(xì)介紹了C++繼承的三種方式和相關(guān)重要概念,整理了眾多繼承與組合中的注意問題。在C++繼承存在不安全的默認(rèn)實(shí)現(xiàn),非虛函數(shù)的覆蓋,多重繼承的函數(shù)名沖突、菱形繼承等眾多問題下,如何實(shí)現(xiàn)多個(gè)功能的自由組合?阿里云高級(jí)開發(fā)工程師采用mixin,為大家提供了更好擴(kuò)展性和更高代碼復(fù)用度的解決方案

摘要:本文詳細(xì)介紹了C++繼承的三種方式和相關(guān)重要概念,整理了眾多繼承與組合中的注意問題。在C++繼承存在不安全的默認(rèn)實(shí)現(xiàn),非虛函數(shù)的覆蓋,多重繼承的函數(shù)名沖突、菱形繼承等眾多問題下,如何實(shí)現(xiàn)多個(gè)功能的自由組合?阿里云高級(jí)開發(fā)工程師采用mixin,為大家提供了更好擴(kuò)展性和更高代碼復(fù)用度的解決方案。
數(shù)十款阿里云產(chǎn)品限時(shí)折扣中,趕緊點(diǎn)擊這里,領(lǐng)劵開始云上實(shí)踐吧!

本次直播視頻精彩回顧,戳這里!?
演講嘉賓簡介:付哲(花名:行簡),阿里云高級(jí)開發(fā)工程師,哈爾濱工業(yè)大學(xué)微電子學(xué)碩士,主攻方向?yàn)榉植际酱鎯?chǔ)與高性能服務(wù)器編程,目前就職于阿里云表格存儲(chǔ)團(tuán)隊(duì),負(fù)責(zé)后端開發(fā)。
以下內(nèi)容根據(jù)演講嘉賓視頻分享以及PPT整理而成。
本文將圍繞一下幾個(gè)方面進(jìn)行介紹:1. C++繼承方式2. 繼承相關(guān)重要概念及注意問題3. 問題及解決:如何組合正交的多個(gè)功能
一. C++繼承方式C++有三種繼承方式:public/protected/private,這三種繼承方式中,派生類都會(huì)繼承基類的public和protected成員,但無法直接訪問基類的private成員,只能通過繼承后的方法來訪問。如圖所示一個(gè)簡單的示例:
在本例中,Base為基類,包含public/protected/private三種成員,其中public和protected成員可以被派生類繼承,而mName不可以被派生類直接訪問,只可以通過繼承后的函數(shù)F(),G(),H(),I()來訪問。
1. Public繼承采用public繼承方式時(shí),基類的成員在派生類中的訪問級(jí)別與基類中一致,即public成員仍是public級(jí)別,protected成員仍是protected級(jí)別。如對(duì)上例中Base進(jìn)行public繼承,得到下派生類:
其中F()為純虛函數(shù),派生類只繼承到函數(shù)的接口,需要再進(jìn)行具體實(shí)現(xiàn);G()為虛函數(shù),派生類同時(shí)繼承了接口和實(shí)現(xiàn);H()為public方法,有實(shí)現(xiàn)但不為虛函數(shù),無法在調(diào)用指針時(shí)觸發(fā)多態(tài),該派生類繼承了接口和強(qiáng)制的實(shí)現(xiàn),這是不能改寫的;I()是protected方法,它不是基類的接口,因此派生類只繼承了它的實(shí)現(xiàn)。此時(shí),該派生類可以作為一個(gè)基類對(duì)象使用,例如上圖中創(chuàng)建派生類對(duì)象用于兩個(gè)函數(shù)中,此時(shí)派生類引用/指針可轉(zhuǎn)換為基類引用/指針。
2. Protected繼承protected繼承與public繼承的不同在于,基類的成員在派生類中的訪問級(jí)別的改變。public和protected成員都成為protected級(jí)別。此時(shí)派生類接口不包含基類的接口,因此protected繼承不是is-a的關(guān)系。繼承后成員如下圖所示:

3. Private繼承private繼承中,基類的public和protected成員都成為private成員。和protected繼承類似,派生類接口不包含基類的接口,因此private繼承也不是is-a的關(guān)系。同時(shí)派生類引用/指針不可轉(zhuǎn)換為基類引用/指針。此外,由于此時(shí)派生類成員都為private,那么后續(xù)派生類型再也無法繼承該類型。對(duì)上例中Base進(jìn)行private繼承,如下圖所示:
那么綜上所述,C++的繼承方式中:public繼承包括基類的接口與實(shí)現(xiàn);protected繼承只包括基類的實(shí)現(xiàn),且可繼續(xù)傳遞;private繼承只包括基類的實(shí)現(xiàn),且不可繼承傳遞。這里值得注意的是,派生類無法繼承基類private成員,這是指派生類無法直接訪問,即基類private成員對(duì)派生類對(duì)象不可見,但在內(nèi)存布局中是包含這些private成員的,且派生類的構(gòu)造、析構(gòu)、復(fù)制也會(huì)受到這些private成員影響。例如假設(shè)基類中有引用private成員,這不僅導(dǎo)致基類方法無法進(jìn)行復(fù)制和移動(dòng),也同樣會(huì)導(dǎo)致派生類的無法復(fù)制和移動(dòng)。
二. 相關(guān)重要概念1. 純虛函數(shù)與抽象類純虛函數(shù)是聲明為等于0的虛函數(shù),具體如下圖所示:
此處0填充在虛表中,這會(huì)導(dǎo)致純虛函數(shù)的虛表為0項(xiàng),即無法創(chuàng)建虛表,無法實(shí)例化。包含純虛函數(shù)的類稱為抽象類,此處Base即為一個(gè)抽象類。但這里需要注意的是,純虛函數(shù)不等于無定義的虛函數(shù)。如果這里將圖中的等于0去掉,即F() = 0改為F(),那么Base類是無法派生的,派生類會(huì)報(bào)錯(cuò)F()無法使用。
2. 接口繼承與實(shí)現(xiàn)繼承接口是類與外界的通信協(xié)議,是抽象的;實(shí)現(xiàn)是類對(duì)協(xié)議的反應(yīng),是具體的。當(dāng)稱派生類繼承了基類的某接口時(shí),表示派生類對(duì)外的協(xié)議中也包含了基類對(duì)外的協(xié)議,調(diào)用該接口時(shí),派生類對(duì)象就會(huì)被當(dāng)做基類對(duì)象使用。當(dāng)稱派生類繼承了基類的某實(shí)現(xiàn)時(shí),指派生類可以調(diào)用基類的某種行為。與Java和C#不同的是,無論是繼承接口還是實(shí)現(xiàn),C++中只有一種繼承語法,并且如上所述,public繼承包括基類的接口與實(shí)現(xiàn),protected和private繼承只包括基類的實(shí)現(xiàn),不包含接口。例如下圖中:
當(dāng)派生類繼承基類public方法時(shí),draw()方法為純虛函數(shù),只能繼承到接口,即派生類必須改寫該函數(shù),否則不能實(shí)例化對(duì)象;error()方法為虛函數(shù),繼承到接口與默認(rèn)實(shí)現(xiàn),即若派生類改寫了該函數(shù),那么該函數(shù)就失效了,若派生類未改寫,則可以直接使用該函數(shù);id()方法為非虛函數(shù),繼承到接口與強(qiáng)制實(shí)現(xiàn),即派生類無法改寫該方法。
3. 安全的默認(rèn)實(shí)現(xiàn)大家可能覺得派生類需要給每個(gè)純虛函數(shù)進(jìn)行實(shí)現(xiàn),太過繁瑣,虛函數(shù)效果更佳。若該純虛函數(shù)較為通用,可能在派生類中需要重寫多遍,而虛函數(shù)提供了一個(gè)默認(rèn)實(shí)現(xiàn),只需在需要時(shí)改寫即可。但請(qǐng)注意,這其實(shí)是非常危險(xiǎn)的。因?yàn)樵诰帉懟悤r(shí)是不知道未來將會(huì)產(chǎn)生哪些派生類,默認(rèn)實(shí)現(xiàn)不一定適用所有派生類。例如下例中:
ModelA和ModelB都可以使用基類的默認(rèn)實(shí)現(xiàn),但后續(xù)加入了不能使用基類的默認(rèn)實(shí)現(xiàn)的ModelC,此時(shí)理應(yīng)為ModelC改寫該實(shí)現(xiàn)但被編程人員遺忘了。編譯時(shí)由于存在基類的默認(rèn)實(shí)現(xiàn),因此不會(huì)報(bào)錯(cuò)。運(yùn)行初期可能不會(huì)出現(xiàn)問題,但會(huì)為后續(xù)埋下非常危險(xiǎn)的隱患。因此嚴(yán)格的代碼規(guī)范中禁止虛函數(shù)提供默認(rèn)實(shí)現(xiàn),即必須使用純虛函數(shù)。但虛函數(shù)可以避免代碼的重復(fù),因此仍存在一定的價(jià)值。實(shí)際運(yùn)用中,存在很多派生類需要使用默認(rèn)實(shí)現(xiàn),那么如何強(qiáng)制要求派生類顯式地使用每個(gè)接口,又可以為派生類準(zhǔn)備一個(gè)可調(diào)用的默認(rèn)實(shí)現(xiàn)呢?一種方法就是為純虛函數(shù)提供定義。這種定義不會(huì)存入虛表,因此基類本身仍然是抽象類,并且派生類仍然需要提供一個(gè)顯式實(shí)現(xiàn)。但更簡便的方法是在派生類中調(diào)用基類的實(shí)現(xiàn),注意此處不能使用虛函數(shù),而是直接使用函數(shù)名調(diào)用。如下圖示例所示:
如此,需要使用默認(rèn)實(shí)現(xiàn)時(shí)只需簡單調(diào)用該函數(shù);而不需要使用默認(rèn)實(shí)現(xiàn)時(shí),若程序員忘記編寫具體實(shí)現(xiàn),編譯時(shí)便會(huì)報(bào)錯(cuò),強(qiáng)制要求提供函數(shù)實(shí)現(xiàn)。因此,便可以避免上述默認(rèn)實(shí)現(xiàn)的隱患。
4. 純接口繼承與接口類純接口繼承是指基類只提供接口,不提供定義,即嚴(yán)格代碼規(guī)范下,基類的所有函數(shù)都是純虛函數(shù),不提供具體實(shí)現(xiàn),派生類需要對(duì)所有方法進(jìn)行自定義,這樣的類型稱為純接口類。純接口繼承完全分離了接口與實(shí)現(xiàn),依賴更少,如下例所示:
這樣的接口類有以下三個(gè)特點(diǎn):一,沒有非靜態(tài)成員變量;二,所有成員都是public成員;三,所有成員都是純虛函數(shù),析構(gòu)函數(shù)除外,因此在上例Interface類中存在一個(gè)有定義的虛的析構(gòu)函數(shù)。純接口繼承的優(yōu)點(diǎn)是最小化調(diào)用處的依賴,且接口與實(shí)現(xiàn)完全分離,這樣在只有實(shí)現(xiàn)發(fā)生變化時(shí),調(diào)用處不會(huì)受到任何影響。而它的缺點(diǎn)是不利于代碼復(fù)用,如果多個(gè)派生類都要實(shí)現(xiàn)相差不多的方法F(),就需要重復(fù)編寫多遍F()的代碼。
5. 確保接口繼承是“is-a”關(guān)系在實(shí)行接口繼承時(shí),需要確保接口繼承是“is-a”關(guān)系。當(dāng)派生類以public方式繼承一個(gè)基類時(shí),它也繼承了這個(gè)基類的所有接口,那么所有使用基類接口處都可以使用派生類。從這個(gè)角度說,派生類對(duì)象是一個(gè)(“is-a”)基類對(duì)象。這也是基類接口對(duì)派生類的約束,派生類需要嚴(yán)格保證其所有行為都符合基類接口的要求。但有時(shí)派生類并沒有達(dá)到這一要求,如下經(jīng)典示例所示:
本例中,基類Bird包含接口fly,正如大家理解,鳥都會(huì)飛。Penguin繼承自Bird,如果采用默認(rèn)實(shí)現(xiàn)那么Penguin也要繼承接口fly。但大家知道企鵝不會(huì)飛,也就意味著它不能有fly接口。如果這里給Penguin一個(gè)空的fly方法,雖然可實(shí)行,但這是不合常理的,必須要向使用者顯示Penguin是不含有fly行為的。如果此處不給出具體實(shí)現(xiàn)的話,只有在運(yùn)行時(shí)才會(huì)拋出異常,這種意外的異常也是不友好的。這個(gè)問題其實(shí)源于對(duì)基類接口的設(shè)計(jì)。當(dāng)已知不是所有鳥都會(huì)飛之后,那么便不應(yīng)該給Bird類一個(gè)fly接口,基類的這種接口是一種不合理的強(qiáng)加的約束。一種解決方法是中間再加一個(gè)層次:Bird類本身是沒有接口的,而Bird的派生類FlyingBird才會(huì)提供fly接口,那么Penguin便繼承Bird類,而不是繼承FlyingBird類。如此若調(diào)用Penguin類的fly接口,編譯時(shí)就會(huì)報(bào)錯(cuò),便可以防止上述問題的發(fā)生。另一個(gè)示例為矩形的實(shí)現(xiàn),如下所示:
長方形類中有一提前假設(shè),即它的長和寬是獨(dú)立的,改變其中一個(gè)值,另一個(gè)值不會(huì)隨之改變。makeBigger就體現(xiàn)了這種假設(shè),這也是對(duì)所有長方形的派生類的要求。但正方形卻不滿足這個(gè)要求,它的長和寬必須是相等的。因此正方形根本不應(yīng)該是長方形的派生類,這種繼承是錯(cuò)誤的。由此可見,C++中的繼承比現(xiàn)實(shí)中的繼承更加嚴(yán)格,需要編程人員謹(jǐn)慎的選擇基類與派生類,任何適用于基類的性質(zhì)都需要適用于派生類。存在任何不滿足“is a”關(guān)系的繼承都是不合理的。
6. 不要覆蓋基類的非虛函數(shù)當(dāng)派生類public繼承一個(gè)有非虛函數(shù)的基類時(shí),派生類也會(huì)繼承這個(gè)非虛函數(shù),并且是繼承了強(qiáng)制實(shí)現(xiàn)。然而與虛函數(shù)不同的是,派生類沒辦法改寫這個(gè)函數(shù),相反,如果自定義編寫一個(gè)同名函數(shù),基類的版本就被“覆蓋了”,如下例所示:
派生類和基類中都有函數(shù)F(),但此處不是改寫而是覆蓋,基類接口被覆蓋會(huì)導(dǎo)致調(diào)用產(chǎn)生的行為不一致。直接通過派生類對(duì)象調(diào)用F()與通過基類指針調(diào)用F(),會(huì)產(chǎn)生不一樣的行為!這種不一致就表明派生類與基類不再是is a的關(guān)系。因此,不要覆蓋基類的非虛函數(shù)。
7. 實(shí)現(xiàn)繼承與組合當(dāng)派生類以protected或private繼承一個(gè)基類時(shí),派生類沒有繼承到基類的接口,而是繼承到了基類的實(shí)現(xiàn)。這種方式被稱為實(shí)現(xiàn)繼承。實(shí)現(xiàn)繼承意味著派生類與基類不是is-a的關(guān)系,而只是需要復(fù)用其實(shí)現(xiàn)或功能。例如,若有一個(gè)類Password,它需要使用std::string功能,那么一種解決方法就是讓它繼承自std::string,如圖所示:
此時(shí)Password類便可以直接調(diào)用string方法。但string類本身并沒有設(shè)計(jì)為可以成為一個(gè)基類,可能存在其析構(gòu)函數(shù)不是虛函數(shù),那么使用基類指針指向析構(gòu)函數(shù)時(shí),會(huì)忽略派生類對(duì)象中的內(nèi)容。但這里還有另一種選擇,那就是將std::string變成Password的一個(gè)成員,而不是Password的基類,這樣仍能使用std::string的各種功能,且不需要增加一種繼承關(guān)系。這種方法被稱為“組合”,它是比繼承更靈活的復(fù)用方法。一般在可以用組合達(dá)到目的時(shí),要盡量避免使用實(shí)現(xiàn)繼承。然而,在某些場(chǎng)景下,實(shí)現(xiàn)繼承有它獨(dú)特的用途。一是在改寫基類的某些功能時(shí),如下例所示:
當(dāng)Widget只需要復(fù)用Timer的其他功能,但不需要Timer的onTick()時(shí),就可以使用private繼承Timer,然后改寫onTick()函數(shù),這是對(duì)象組合無法輕易完成的。當(dāng)然該場(chǎng)景下,結(jié)合內(nèi)部類,組合也可以達(dá)到類似效果。用內(nèi)部類private繼承Timer,與Widget構(gòu)成組合關(guān)系,如此來避免Widget直接繼承Timer。第二種場(chǎng)景是當(dāng)基類是空類型時(shí),繼承可以應(yīng)用到空基類優(yōu)化,在派生類中不占空間,而對(duì)象組合則沒有這種優(yōu)化,空類型成員至少要占用一字節(jié)的空間,一般會(huì)在八字節(jié)及以上。該場(chǎng)景最經(jīng)典的例子是boost::noncopyable,無論是private繼承,還是將其作為成員變量,都可以令自定義類型無法復(fù)制,但private繼承時(shí),因?yàn)樵摶愂强疹愋?#xff0c;因此在派生類中不占空間。實(shí)現(xiàn)繼承的該特性在標(biāo)準(zhǔn)類型庫中被廣泛使用。
8. 多重繼承的問題C++允許一個(gè)派生類繼承自多個(gè)基類,但逐漸大家意識(shí)到這種自由會(huì)導(dǎo)致一些棘手的問題,因此Java等語言取消了這種特性,而是派生類只允許有一個(gè)基類。多重繼承會(huì)導(dǎo)致以下兩個(gè)問題。一是不同基類間的名字沖突或者歧義,如下例所示:
此處有A和B兩基類,C同時(shí)繼承A和B。雖然A類中的Func()是public函數(shù),B中的func()是private函數(shù),但調(diào)用C類的func()函數(shù)時(shí),它理應(yīng)調(diào)用A的func(),但C++的名字查找規(guī)則是優(yōu)先于訪問級(jí)別檢查的,因此先查找名字,進(jìn)行重載決議,再檢查訪問級(jí)別,而在重載決議時(shí)編譯器檢查到了兩個(gè)相同優(yōu)先級(jí)的func(),這就產(chǎn)生了沖突。解決這個(gè)問題就需要顯式調(diào)用某個(gè)基類的版本,指定調(diào)用函數(shù)的namespace:
第二個(gè)問題是菱形繼承問題,這比上述問題更加棘手。當(dāng)語言允許多繼承時(shí),一個(gè)基類可能會(huì)多次出現(xiàn)在同一個(gè)派生類的基類樹中,如下例所示:
InputFile和OutputFile同時(shí)繼承于File類,而下一層IOFile類同時(shí)繼承InputFile和OutputFile,即繼承了兩次File,這就是菱形繼承。當(dāng)出現(xiàn)菱形繼承時(shí),意味著派生類對(duì)象中有多個(gè)相同類型的基類子對(duì)象,此時(shí)調(diào)用該基類方法時(shí)會(huì)產(chǎn)生如何選擇子對(duì)象的混亂。并且有些屬性對(duì)派生類是唯一的,比如File屬性,在IOFile中只應(yīng)有一份。為了解決這個(gè)問題,C++增加了虛繼承,虛繼承的基類在派生類中只會(huì)有一份。但虛繼承被認(rèn)為是比多重繼承更糟糕的特性,它比虛函數(shù)的開銷更大,且反直覺地要求最終派生類型的構(gòu)造函數(shù)來構(gòu)造整個(gè)繼承鏈條中所有虛繼承的基類。因此,為了避免菱形繼承,一些編程規(guī)范規(guī)定:不能使用虛繼承;盡量不要使用多重繼承;如果要用多重繼承,盡量模仿Java語言,至多只能有一個(gè)基類有實(shí)現(xiàn),其它基類都是接口類。
三. 問題及解決:如何組合正交的多個(gè)功能假設(shè)有若干個(gè)彼此獨(dú)立,或說正交的功能,如何將它們組合起來?例如有一個(gè)TaskManager類,負(fù)責(zé)管理所有擁有ITask接口的對(duì)象,如下所示:
現(xiàn)在需要為ITask類型增加兩個(gè)功能:一是timing功能,即在ITask對(duì)象執(zhí)行Execute方法前后計(jì)時(shí);二是logging功能,即在ITask對(duì)象對(duì)待Execute方法前后打印日志。那這該如何解決呢?
1. 繼承第一種方式是通過繼承復(fù)用功能。具體如下所示:
從ITask類派生出一個(gè)ILoggingTask類,它增加OnExecute()接口,其派生類只要實(shí)現(xiàn)這個(gè)接口,在調(diào)用ITask::Execute時(shí)就能打印日志了。同樣的方法,這里也通過增加ITimingTask類實(shí)現(xiàn)timing的功能:
然而當(dāng)需要同時(shí)復(fù)用timing和logging該如何解決呢?假設(shè)使用上述方法,那么ITimingTask的Execute()與ILoggingTask的Execute()是沖突的,無法同時(shí)復(fù)用兩個(gè)功能。這就體現(xiàn)了通過繼承來復(fù)用代碼的缺陷,對(duì)于單個(gè)功能,可以將需要復(fù)用的實(shí)現(xiàn)代碼放在基類中,但如果需要同時(shí)復(fù)用多個(gè)功能,通過繼承復(fù)用功能就無法解決了。另外,本例中因?yàn)樵黾恿艘粚犹摵瘮?shù),而且還是在虛函數(shù)中調(diào)用另一個(gè)虛函數(shù),這就導(dǎo)致編譯器無法inline代碼,從而增加運(yùn)行期的開銷。
2. 組合第二種方式是通過組合復(fù)用功能。具體如下所示:
這里L(fēng)oggingTask不再作為基類存在,而是作為代理,把對(duì)LoggingTask的請(qǐng)求轉(zhuǎn)發(fā)給它持有的task成員,由task來解決請(qǐng)求。同樣地,這里也增加一個(gè)TimingTask類:
接下來就可以通過鏈?zhǔn)絺鬟f來組合這兩個(gè)功能。
通過組合來復(fù)用功能仍然也存在一些問題。一是這種方法依然有一些運(yùn)行期的開銷,比如需要在堆上分配每個(gè)對(duì)象,多次調(diào)用虛函數(shù)。但它解決了組合多個(gè)功能的問題,不同功能間也耦合較低。二是LonggingTask需要實(shí)現(xiàn)一些不用的接口。像LoggingTask這樣純粹的功能,本是不需要實(shí)現(xiàn)GetName這樣的接口,但它繼承自ITask,就需要實(shí)現(xiàn)ITask所有的接口。假如ITask還有其它接口,LoggingTask也都需要實(shí)現(xiàn),這就增加了代碼的復(fù)雜度,使得該模塊特別臃腫。那么這該如何解決呢?
3. 重返繼承這次仍然嘗試用繼承來解決該問題,但與上述第一種繼承方法相比,做出一些變化。前面的方法中增加了兩個(gè)基類,且把需要復(fù)用的部分放在基類中。而這里把需要復(fù)用的部分放在派生類中:
這里將MyTask作為基類,TimingTask中繼承MyTask來執(zhí)行計(jì)時(shí)的操作。而LoggingTask繼承TimingTask,如此LoggingTask便同時(shí)具有計(jì)時(shí)和打印兩個(gè)功能。但這種方法仍然有很多缺陷:一是不同功能之間因?yàn)槔^承完全耦合在一起,功能之間無法分割;二是這兩個(gè)功能綁定在MyTask類中,導(dǎo)致這兩個(gè)功能完全無法被其它類型復(fù)用。雖然有這么多致命缺陷,但這種方法仍然有獨(dú)特的優(yōu)勢(shì):首先,不需要堆分配;其次,沒有多余的虛函數(shù)定義及其調(diào)用;最后,編譯器有機(jī)會(huì)做更多內(nèi)聯(lián)優(yōu)化。那么,該如何解決這個(gè)方法的缺點(diǎn)呢?由代碼分析可知,上例中的兩個(gè)缺點(diǎn)都是因?yàn)榛愂枪潭ǖ?#xff0c;無法變化,如果能用模板將基類作為參數(shù)傳遞,上述缺點(diǎn)便解決了。
4. Mixin最后一種方法是通過mixin復(fù)用功能。mixin本身是面向?qū)ο箢I(lǐng)域的一個(gè)非常寬泛的概念,它是有一系列被稱為mixin的類型,這些類型分別實(shí)現(xiàn)一個(gè)單獨(dú)的功能,且這些功能本身是正交的。當(dāng)需要使用這些功能時(shí),就可以將不同的mixin組合在一起,像搭積木一樣,完成功能復(fù)用。一個(gè)更清晰的解釋是這樣的:一個(gè)mixin就是類里的一小塊,可以用來與其它類或mixin做組合;一個(gè)獨(dú)立的類與一個(gè)mixin的區(qū)別在于,一個(gè)mixin只建模小的功能點(diǎn)(如timing或printing),并不是用來獨(dú)立使用,而是給其它需要這個(gè)功能的類做組合。在C++中最常用的實(shí)現(xiàn)mixin的方式叫“參數(shù)化模板”。這里可以將TimingTask和LoggingTask的基類都換成模板參數(shù):
此時(shí)便可以解決前述所有問題,TimingTask和LoggingTask實(shí)現(xiàn)完全不耦合,所有代碼獨(dú)立,且所有含有Execute()方法的類型都可以組合這兩個(gè)功能。組合過程如下所示:
首先新建MyTask對(duì)象,將該對(duì)象傳遞進(jìn)入TimingTask類中,生成對(duì)象t1,然后可以將t1傳遞進(jìn)LoggingTask類中生成對(duì)象t2。t2便同時(shí)具備Timing和Logging功能。C++可以通過繼承方法來支持mixin,而不像其他語言,如Ruby,顯式的支持mixin。如此使用mixin便能夠?qū)崿F(xiàn)自由組合多個(gè)功能,并且囊括了之前方法的所有優(yōu)點(diǎn),有更好的擴(kuò)展性和更高的代碼復(fù)用度。當(dāng)然這個(gè)類并不是最終希望得到的,因?yàn)樗鼪]有實(shí)現(xiàn)ITask接口。因此仍然可以增加一個(gè)新的mixin,來將任意含有Execute和GetName方法的類型適配為ITask的派生類:

本文由云棲志愿小組郭雪整理,編輯百見

原文鏈接

干貨好文,請(qǐng)關(guān)注掃描以下二維碼:



總結(jié)

以上是生活随笔為你收集整理的C++继承和组合——带你读懂接口和mixin,实现多功能自由组合的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。