Symbian OS 中的Class命名約定(M類)
計(jì)算機(jī)界的民間傳說在談到“mix-ins”的起源時(shí),普遍認(rèn)為這一概念最早源于Symbolic's Flavors——一個(gè)早期的面向?qū)ο缶幊滔到y(tǒng)。它的設(shè)計(jì)者顯然從“史蒂夫的冰淇淋客廳”(麻省理工學(xué)院的學(xué)生們特別喜歡的一個(gè)冰淇店)中獲得了靈感,顧客們在那里選擇不同冰淇淋的風(fēng)味(香草,草莓,巧克力等等)然后再加入一些混合物(堅(jiān)果, 奶油軟糖, 巧克力碎屑等等)。
當(dāng)談到多重繼承時(shí),這就意味著在基礎(chǔ)的“風(fēng)味”類中加入“mix-in”類來擴(kuò)展它的功能。我相信這就是Symbian的設(shè)計(jì)者使用mixin這一術(shù)語并且把M設(shè)為前綴的原因,盡管在Symbian OS中的多重繼承和mixin的使用要比冰淇淋店中要復(fù)雜得多。
總體而言,M類是一個(gè)抽象接口類。一個(gè)具體類(concrete class)通常把基礎(chǔ)的“風(fēng)味”類(例如CBase或CBase派生類)作為它的***個(gè)基類,然后再加入一個(gè)或一個(gè)以上的M類“mixin”接口來實(shí)現(xiàn)接口函數(shù)。在Symbian OS中,M類通常定義為回調(diào)(callback)接口或監(jiān)聽器(observer)接口。
一個(gè)M類可以由其它M類繼承得來,下面我給出了兩個(gè)例子。***個(gè)展示了一個(gè)實(shí)現(xiàn)接口函數(shù)的具體類派生至CBase和一個(gè)mixin類—— MDomesticAnimal,MdomesticAnimal類派生于MAnimal類但并沒有對它進(jìn)行實(shí)現(xiàn),實(shí)際上,它是MAnimal接口的一個(gè)特化(specialization)。
{
public:
virtual void EatL() =0;
};
class MDomesticAnimal : public MAnimal
{
public:
virtual void NameL() =0;
};
class CCat : public CBase,public MDomesticAnimal
{
public:
virtual void EatL(); // 通過MDomesticAnimal繼承自MAnimal
virtual void NameL(); // 繼承自MDomesticAnimal
…………………………
};
第二個(gè)示例展示了CBase的派生類和兩個(gè)mixin接口,MRadio和MClock。在這種情況下,MClock并不是MRadio的特化,CClockRadio這個(gè)具體類分別繼承并實(shí)現(xiàn)了這兩個(gè)接口。對于M類繼承而言,這種復(fù)雜組合的多重繼承是可以實(shí)現(xiàn)的。
{
public:
virtual void TuneL() =0;
};
class MClock
{
public:
virtual void CurrentTimeL(TTime& aTime) =0;
};
class CClockRadio : public CBase, public MRadio, public MClock
{
public:
virtual void TuneL();
virtual void CurrentTimeL(TTime& aTime);
…………………………
};
上面展示了多重接口繼承的使用,這也是在Symbian OS中***鼓勵使用的一種多重繼承形式。其它形式的多重繼承將會產(chǎn)生相當(dāng)復(fù)雜的繼承分級,并且標(biāo)準(zhǔn)基類也沒有考慮這方面的設(shè)計(jì)問題。因而,對兩個(gè) CBase派生類進(jìn)行多重繼承將會在編譯時(shí)引起二義性問題,只能通過虛擬繼承來解決這一問題:
{…………………………};
class CClass2 : public CBase
{…………………………};
class CDerived : public CClass1,public CClass2
{…………………………};
void TestMultipleInheritance()
{
// 無法編譯, CDerived::new 有二義性
// 應(yīng)明確定義CBase::new繼承自CClass1還是CClass2?
CDerived* derived = new (ELeave) CDerived();
}
讓我們來考慮一下M類的特性,它可能被認(rèn)為等價(jià)于java語言中的接口。和java接口一樣,M類沒有數(shù)據(jù)成員,所以M類對象不需要進(jìn)行初始化,因而也就不必為它編寫構(gòu)造函數(shù)。
自然而然的,你也會謹(jǐn)慎的考慮M類是否應(yīng)該有析構(gòu)函數(shù)(無論是虛的還是非虛的)。M類的析構(gòu)函數(shù)給它的接口實(shí)現(xiàn)方式增加了限制條件,使得它必須在 CBase 的派生類中實(shí)現(xiàn)。這是因?yàn)槲鰳?gòu)函數(shù)就意味著調(diào)用delete,因而就要求對象必須基于heap而不能基于stack建立。這就是說實(shí)現(xiàn)類一定派生于 CBase,就是因?yàn)檫@一原因,所有的T類和絕大部分的R類都不存在析構(gòu)函數(shù)。
在通過一個(gè)指向接口類的指針擁有一個(gè)M類對象時(shí),需要提供一些銷毀M類對象的方法。如果你確信能夠保證你的接口類的實(shí)施類一定派生于CBase類,那你可以在M類中提供一個(gè)虛析構(gòu)函數(shù)。這樣就可以讓M類的所有者通過調(diào)用delete來刪除它。
如果不定義虛析構(gòu)函數(shù),對一個(gè)M類指針進(jìn)行delete操作將會引發(fā)USER 42 panic,這是因?yàn)椋号缮贛類的具體類必然也派生于其它類,如CBase或CBase的派生類,mixin指針應(yīng)在繼承聲明次序中排在C類之后 [1],并且這個(gè)M類的指針會轉(zhuǎn)而指向M類的子對象,而不是指向有效主對象heap單元的起始位置,這個(gè)M類的子對象是用于訪問分配到heap單元上的對象的。
可以使用內(nèi)存管理代碼來釋放heap空間,User::Free(),但如果待清除單元的定位錯誤將會引發(fā)panic。這一問題可以通過使用虛析構(gòu)函數(shù)來解決。
但是,定義M類接口是并不是惟一的解決方案,你也可以考慮定義一個(gè)派生于CBase的抽象基類來代替M類。因?yàn)镃Base恰好已經(jīng)提供了一個(gè)虛析
1 具體類定義格式為
而不是
“風(fēng)味”C類必須在基類列表的***位,以此強(qiáng)調(diào)繼承樹的次序。類似這樣的C類對象也可以通過重載CleanupStack::PushL()來壓入清潔棧(參見第3章)
構(gòu)函數(shù),并且接口類的實(shí)施類也僅限于在C類中實(shí)現(xiàn),所以這也是一個(gè)理想的辦法。當(dāng)然,在這一方法中,實(shí)施類將僅限于單重繼承(single inheritance),就象在上面所說的那樣,對CBase的多重繼承將會引起二義性和可怕的“菱形”繼承結(jié)構(gòu)。
總體而言,一個(gè)mixin接口類不應(yīng)關(guān)心ownership的實(shí)現(xiàn)細(xì)節(jié)。如果調(diào)用者有可能通過指向M類接口的指針擁有一個(gè)對象,正如上面所說的那樣,你就必須確保提供一個(gè)在M類的所有者在不再需要M類時(shí)刪除它的方法。但是,這并不限于通過析構(gòu)函數(shù)進(jìn)行清除。你也可以提供一個(gè)純虛Release()函數(shù)來代替析構(gòu)函數(shù),然后類的所有者就可以說“搞定了”——由實(shí)現(xiàn)代碼決定使用何種清除方式(例如一個(gè)C類,就可以調(diào)用“delete this”來完成)。這是一個(gè)更靈活的接口——實(shí)施類基于stack或heap實(shí)現(xiàn)都可以,它可以實(shí)現(xiàn)引用計(jì)數(shù),專門的清除或其它等等的功能。順便說一句,它實(shí)質(zhì)上并不能調(diào)用清除函數(shù)Release()或Close(),但它可以幫助你的調(diào)用者。首先,它是可識別的并足以讓你推測出它將要干什么。更重要的是,它能夠讓用戶使用CleanupReleasePushL()函數(shù)(參見第3章)。
類似于java的接口,一個(gè)M類通常僅有純虛函數(shù)。但是,也有例外允許使用非純虛函數(shù)。比如說:當(dāng)某個(gè)接口的所有繼承類都具有通用的缺省行為時(shí),給這個(gè)接口添加共享的實(shí)現(xiàn)就可以減少代碼的冗余和維護(hù)等令人頭痛的事情。當(dāng)然,這個(gè)缺省的通用實(shí)現(xiàn)功能有很多限制,因?yàn)閙ixin類不能有數(shù)據(jù)成員。所以通常情況下,所有的虛函數(shù)都是通過調(diào)用mixin接口的純虛函數(shù)實(shí)現(xiàn)功能的。
【編輯推薦】