今天你多态了吗? 【转】
?
今天你多態(tài)了嗎?
Do You Polymorphism Today? [0]
?
Written by Allen Lee
?
-1. 目錄
- -1. 目錄
- 0. 寫在前面的話。
- 0.0 關(guān)鍵字。
- 0.1 系統(tǒng)要求?!
- 0.2 如何閱讀本文?
- 1. 圖書館魔術(shù)事件簿。
- 1.0 圖書管理員的煩惱。
- 1.1 魔術(shù)棒是如何工作的?
- 1.2 魔術(shù)般真能起作用嗎?
- 1.3 我們在干什么?
- 1.4 Poly呢?
- 2. 多態(tài)為何物?
- 2.0 真實的多態(tài)。
- 2.1 多態(tài)的種類。
- 2.2 多態(tài)失效了?
- 2.3 要睡覺了嗎?
- 3. 多態(tài)與重構(gòu)。
- 3.0 請別妨礙我們改善公司的組織結(jié)構(gòu)!
- 3.1 Poly的噩夢——圖書館的倒塌。
- 4. 完結(jié)·新篇。
- 5. 參考書目。
- X. 注釋。
?
0. 寫在前面的話。
0.0 關(guān)鍵字。
- 中文:多態(tài),面向?qū)ο?#xff0c;繼承,封裝,抽象類,重構(gòu),基類,基類繼承多態(tài),接口,接口繼承多態(tài),抽象方法,虛方法,重載方法,繼承體系,插件系統(tǒng),引用類型,值類型,接口多重繼承
- 英文:Polymorphism, Object-Oriented, Inheritance, Encapsulation, abstract class, base class, Refactoring, interface, abstract method, virtual method, overriden method, override, new, Null Object Pattern, reference type, value type, switch
0.1 系統(tǒng)要求?!
- 0.1.0 使用面向?qū)ο蠹夹g(shù)(至少有使用的打算)。
- 0.1.1 對C#有一個基本的了解(我指的是語法以及相關(guān)的概念)。
- 0.1.2 初步了解面向?qū)ο蟮睦^承以及封裝(如果沒有了解...就看著辦吧!)。
- 0.1.3 希望了解多態(tài)及其相關(guān)的內(nèi)容。
- 0.1.4 必要的耐性(至少你應(yīng)該看完“如何閱讀本文?”。連“如何閱讀本文?”都看不進?關(guān)掉瀏覽器吧!)。
- 0.1.5 其它......
0.2 如何閱讀本文?
?
switch(you.Purpose){
??case?"希望讀一個關(guān)于多態(tài)的故事“
????if(you.IsPatient)
??????goto?"1.?圖書館魔術(shù)事件簿";
??case?"因為《readonly?vs.?const》的Remark的最后一點而來":
????goto?"2.0?真實的多態(tài)";
??case?"僅希望了解一下多態(tài)的種類":
????goto?“2.1?多態(tài)的種類”
??case?"僅希望了解一下不同種類的多態(tài)的注意事項":
????goto?“2.2?多態(tài)失效了?”
??case?"只想知道我是如何回答'何謂多態(tài)?'":
????goto?“2.3?要睡覺了嗎?”;
??case?"希望了解多態(tài)如何幫助你實現(xiàn)更靈活的條件擴展機制":
????goto?“3.0?請別妨礙我們改善公司的組織結(jié)構(gòu)”;
??case?"希望了解Null?Object模式以及Introduce?Null?Object重構(gòu)原則如何發(fā)揮多態(tài)的威力":
????goto?“3.1?Poly的噩夢——圖書館的倒塌”;
??case?"想聽聽我對多態(tài)的閑談":
????goto?“4.?完結(jié)·新篇”;
??case?"只想活動一下因使用鼠標(biāo)而勞累的手":
????goto?"關(guān)閉你的瀏覽器";
??default:
????goto?"從頭到尾閱讀本文";
}
?
1. 圖書館魔術(shù)事件簿[1]。
1.0 圖書管理員的煩惱。
- Poly:學(xué)校這幾年擴招,圖書館也有一大批新書入庫,而管理員卻還是只有我們兩個,每天都忙個不停。
- Morph:沒辦法啦,這總比失業(yè)好吧?
- Poly:哎,每天單處理歸還的書的工作就叫人喘不了氣了,如果這些書能夠自動飛回各自所屬的位置該多好呀!
- Morph:來,我賜你一把魔術(shù)棒,這樣你就不用那么辛苦了。
- Poly:拜托!不要拿支牙刷來耍我,今天的工作恐怕又干不完了,沒空跟你開玩笑呀!
- Morph:呵呵,誰叫你有這種閑情發(fā)白日夢?
1.1 魔術(shù)棒是如何工作的呢?
剛剛才被Morph耍完的Poly現(xiàn)在又在發(fā)白日夢了,他想著Morph提到的魔術(shù)棒,自言自語:“如果真的有這么一支魔術(shù)棒,它將如何工作呢?”
他聯(lián)想到自己平時工作的情況,認(rèn)為要把圖書歸類好,必須滿足以下兩個條件:
- a) 每本圖書都應(yīng)該具有一些信息,這些信息描述了該書的所屬類別以及存放地點等;
- b) 有一頭任勞任怨的牛根據(jù)這些信息把書拿到存放地點放好。
想著想著,Poly奏起眉頭了:“這么說來,我不就是那頭牛?不行,我得讓魔術(shù)棒幫幫我!”于是,Poly又陷入沉思了:如果這些書懂得自己飛回它所屬的存放地點的話......對了,得讓魔術(shù)棒幫我這個忙!
1.2 魔術(shù)棒真能起作用嗎?
1.2.0 現(xiàn)實中的Poly是如何工作的?
class?Poly{
??static?void?Main()
??{
????//?圖書館早上準(zhǔn)時開門;
????Library?library?=?new?Library();
????//?當(dāng)然,Poly他們沒有理由只開圖書館的大門,他們還要開放圖書館的各層樓。
????//?一樓的門就是大門了,要開兩次大門嗎?
????Floor?floor2?=?library.Floor2;
????Floor?floor3?=?library.Floor3;
????Floor?floor4?=?library.Floor4;
????
????//?同學(xué)們一大早就歸還很多圖書。
????List<Book>?books?=?new?List<Book>();
????//?省略填充books列表的代碼
????//?
????//?我們認(rèn)為Poly會先把書分類好屬于哪一層樓,然后再一次過搬到對應(yīng)的樓層,
????//?而不是傻乎乎的每拿到一本書就跑到對應(yīng)的樓層并把它放入對應(yīng)的書架!
????List<Book>?floor2Car?=?new?List<Book>();
????List<Book>?floor3Car?=?new?List<Book>();
????List<Book>?floor4Car?=?new?List<Book>();
????
????//?Poly需要根據(jù)每本書上的信息進行處理
????foreach(Book?book?in?books)
????{
??????//?首先看看該書屬于圖書館那一層樓的。
??????switch(book.FloorNumberString)
??????{
????????//?一樓是圖書館辦公室和借書柜臺,不用于存放圖書。
????????case?"2nd":
??????????//?把書放進往二樓的推車?yán)铩?/span>
??????????floor2Car.Add(book);
??????????break;
????????case?"3rd":
??????????//?把書放進往三樓的推車?yán)铩?/span>
??????????floor3Car.Add(book);
??????????break;
????????case?"4th":
??????????//?把書放進往四樓的推車?yán)铩?/span>
??????????floor4Car.Add(book);
??????????break;
????????//?default:
????????//??//?把書放進哪里?地下室?還是天臺?
????????//??break;
??????}
????}
????//?接著Poly把往二樓的車推到二樓。
????//?我們認(rèn)為Poly會先把書按照所屬類別分類好,放在裝書籃里,
????//?而不是傻乎乎的每拿到一本書就往對應(yīng)的書架跑。
????List<Book>?mathBasket?=?new?List<Book>();
????List<Book>?physicsBasket?=?new?List<Book>();
????List<Book>?chemistryBasket?=?new?List<Book>();
????foreach(Book?book?in?floor2Car)
????{
??????switch(book.Catalog)
??????{
????????case?"Math":
??????????mathBasket.Add(book);
??????????break;
????????case?"Physics":
??????????physicsBasket.Add(book);
??????????break;
????????case?"Chemistry":
??????????chemistryBasket.Add(book);
??????????break;
??????}
????}
????//?然后Poly提著籃子跑到對應(yīng)的書架
????foreach(Book?book?in?mathBasket)
??????floor2.MathShelves.Add(book)
????foreach(Book?book?in?physicsBasket)
??????floor2.PhysicsShelves.Add(book);
????foreach(Book?book?in?chemistryBasket)
??????floor2.ChemistryShelves.Add(book);
????//?接著Poly把往三樓的車推到三樓。
????//?我們同樣認(rèn)為Poly會先把書按照所屬類別分類好,放在裝書籃里,
????//?而不是傻乎乎的每拿到一本書就往對應(yīng)的書架跑。
????List<Book>?economicsBasket?=?new?List<Book>();
????List<Book>?marketingBasket?=?new?List<Book>();
????List<Book>?managementBasket?=?new?List<Book>();
????foreach(Book?book?in?floor3Car)
????{
??????switch(book.Catalog)
??????{
????????case?"Economics":
??????????economicsBasket.Add(book);
??????????break;
????????case?"Marketing":
??????????marketingBasket.Add(book);
??????????break;
????????case?"Management":
??????????managementBasket.Add(book);
??????????break;
??????}
????}
????//?然后Poly提著籃子跑到對應(yīng)的書架
????foreach(Book?book?in?economicsBasket)
??????floor3.EconomicsShelves.Add(book)
????foreach(Book?book?in?marketingBasket)
??????floor3.MarketingShelves.Add(book);
????foreach(Book?book?in?managementBasket)
??????floor3.ManagementShelves.Add(book);
????//?接著Poly把往四樓的車推到四樓。
????//?如果Poly傻乎乎的每拿到一本書就往對應(yīng)的書架跑,那我們只好讓他跑了。
????List<Book>?programmingBasket?=?new?List<Book>();
????List<Book>?seBasket?=?new?List<Book>();
????List<Book>?databaseBasket?=?new?List<Book>();
????List<Book>?networkBasket?=?new?List<Book>();
????foreach(Book?book?in?floor4Car)
????{
??????switch(book.Catalog)
??????{
????????case?"Programming":
??????????programmingBasket.Add(book);
??????????break;
????????case?"Software?Engineering":
??????????seBasket.Add(book);
??????????break;
????????case?"Database":
??????????databaseBasket.Add(book);
??????????break;
????????case?"Network":
??????????networkBasket.Add(book);
??????????break;
??????}
????}
????//?然后Poly提著籃子跑到對應(yīng)的書架
????foreach(Book?book?in?programmingBasket)
??????floor4.ProgrammingShelves.Add(book)
????foreach(Book?book?in?seBasket)
??????floor4.SEShelves.Add(book)
????foreach(Book?book?in?physicsBasket)
??????floor4.DatabaseShelves.Add(book);
????foreach(Book?book?in?chemistryBasket)
??????floor4.NetworkShelves.Add(book);
????//?最后,Poly整個人躺在地上
??}
}
1.2.1 Poly,我終于理解你的吶喊了!
說實話,直道我完成這個代碼,我才真正明白為何Poly一直在煩悶,我想,這個代碼應(yīng)該可以令校方領(lǐng)導(dǎo)下決心改善圖書館的工作環(huán)境,至少也得請多幾個人來分擔(dān)一下工作。否則,某天學(xué)校突然決定要為圖書館加樓層、增加新的圖書類別或者調(diào)整圖書現(xiàn)有類別時,Poly會毅然決定逃去參軍(失業(yè)大軍)!不信,你試著把第二書店的電腦書部分分類加入到圖書館的4樓,合并3樓現(xiàn)有的類別,并增加文學(xué)、宗教等系列的新類別,然后......怎么樣?有什么感覺?牽一發(fā)而動全身!
1.2.2 Poly決定揮動手上的魔術(shù)棒:
Poly手上的魔術(shù)棒叫什么名字呢?既然是Morph“賜”的,就叫他Morphism吧!好了,Poly揮動他手上的Morphism魔術(shù)棒...
class?Polymorphism{
??static?void?Main()
??{
????//?圖書館早上準(zhǔn)時開門;
????Library?library?=?new?Library();
????//?怎么每天只需開大門其他樓層的門就自動打開了嗎?
????//?Floor?floor1
????//?同學(xué)們一大早就歸還很多圖書。
????List<Book>?books?=?new?List<Book>();
????//?省略填充books列表的代碼
????//?
????//?果然每本書自己飛回各自所屬的原位喲。
????foreach(Book?book?in?books)
????{
??????book.Return(library);
????}
??}
}
abstract?class?Book
{
??public?abstract?void?Return(Library?library);
}
abstract?class?Floor2Book?:?Book?{?}
abstract?class?Floor3Book?:?Book?{?}
abstract?class?Floor4Book?:?Book?{?}
class?MathBook?:?Floor2Book
{
??public?override?void?Return(Library?library)
??{
????library.Floor2.MathShelves.Add(this);
??}
}
class?PhysicsBook?:?Floor2Book
{
??//?Code?here
}
class?ChemistryBook?:?Floor2Book
{
??//?Code?here
}
class?ManagementBook?:?Floor3Book
{
??public?override?void?Return(Library?library)
??{
????library.Floor3.ManagementShelves.Add(this);
??}
}
class?EconomicsBook?:?Floor3Book
{
??//?Code?here
}
class?MarketingBook?:?Floor3Book
{
??//?Code?here
}
class?ProgrammingBook?:?Floor4Book
{
??public?override?void?Return(Library?library)
??{
????library.Floor4.ProgrammingShelves.Add(this);
??}
}
class?SoftwareEngineeringBook?:?Floor4Book
{
??//?Code?here
}
class?DatabaseBook?:?Floor4Book
{
??//?Code?here
}
class?NetworkBook?:?Floor4Book
{
??//?Code?here
}
下面是魔術(shù)棒所產(chǎn)生的副產(chǎn)品[2]:
1.2.3 檢驗?zāi)g(shù)棒的效果。
1.2.3.0 加入新電腦書籍分類。
class?WebDevelopmentBook?:?Floor4Book{
??public?override?void?Return(Library?library)
??{
????library.Floor4.WebDevelopmentShelves.Add(this);
??}
}
當(dāng)然,你需要在4樓騰出地方來放一個(或多個)新的書架來安放Web Development類的書籍。
1.2.3.1 合并3樓現(xiàn)有的分類。
class?BusinessBook?:?Floor3Book{
??public?override?void?Return(Library?library)
??{
????library.Floor3.BusinessShelves.Add(this);
??}
}
當(dāng)然,你需要把Economics、Marketing、Management三類書的書架放在一起(或者你有更好的是這些書放在一起的方法),把原本書架的這三個標(biāo)簽撕掉,重新貼上Business標(biāo)簽。
1.2.3.2 學(xué)校要加蓋第五層樓?
abstract?class?Floor5Book?:?Book?{?}當(dāng)然,你需要為5樓添加設(shè)施和設(shè)定圖書分類,并放入一些書。
1.3 我們在干什么?
實質(zhì)上我們在重構(gòu)(Refactoring)!只是前后兩種代碼哪種來的更清晰以及更可讀而已。當(dāng)然,在這個重構(gòu)的過程中,我們很難避免改動圖書館(Library)以及各層樓的公共設(shè)施(公共成員),所以,必須小心行事!
說實話,這并不是一個好的例子,因為書本的行為與圖書館結(jié)構(gòu)和設(shè)施的細(xì)節(jié)有太多的藕斷絲連了。所以,如果我們能夠為他們找一個中間人(第三者?)來處理這些復(fù)雜關(guān)系(別說我壞心腸棒打鴛鴦呀!),避免過分的糾纏就好了。不過,作為一個開場白,我想這應(yīng)該夠了吧!?
1.4 Poly呢?
- Poly:“哎呀!好疼喲!誰用書敲我的頭?”
- Morph:“你真過分,居然偷懶在這里大睡?”
- Poly:“魔術(shù)棒...”
- Morph:“什么魔術(shù)棒?你拿著這支牙刷干什么嗎?”
- Poly:“......”
?
2. 多態(tài)為何物?
2.0 真實的多態(tài)。
我曾經(jīng)在《readonly vs. const》一文提到下面這句話:
然而,當(dāng)這種結(jié)合使用枚舉和條件判斷的代碼阻礙了你進行更靈活的擴展,并有可能導(dǎo)致日后的維護成本增加,你可以代之以多態(tài),使用Replace Conditional with Polymorphism來對代碼進行重構(gòu)。
下面我來試一下用多態(tài)實現(xiàn)會員分級制,首先我們來看看UML類圖:
接著看看對應(yīng)的代碼:
class?Order{
??public?Order(Customer?customer)
??{
????ID?=?Guid.NewGuid().ToString();
????m_Customer?=?customer;
????m_Products?=?new?List<Product>();
??}
??public?readonly?string?ID;
??private?Customer?m_Customer;
??private?List<Product>?m_Products;
??public?void?AddProduct(Product?product)
??{
????m_Products.Add(product);
??}
??public?double?TotalAmount()
??{
????double?totalAmount?=?0.0;
????foreach(Product?product?in?m_Products)
??????totalAmount?+=?product.Price;
????return?totalAmount?*?m_Customer.Discount;
??}
??public?override?string?ToString()
??{
????return?"Dear?"?+?m_Customer.Name?",\n"?+
???????????"\tTotal?amount?of?order?"?+?ID?+
???????????"?is?"?+?TotalAmount().ToString();
??}
}
abstract?class?Customer
{
??public?abstract?string?Name?{?get;?}
??public?abstract?double?Discount?{?get;?}
}
class?SuperVipCustomer?:?Customer
{
??public?SuperVipCustomer(string?name)
??{
????m_Name?=?name;
??}
??private?string?m_Name;
??public?override?string?Name
??{
????get?{?return?m_Name;?}
??}
??private?const?double?c_Discount?=?0.65;
??public?override?double?Discount
??{
????get?{?return?c_Discount;?}
??}
??public?override?string?ToString()
??{
????return?"Name:?"?+?m_Name?+?"[SuperVip]";
??}
}
class?VipCustomer?:?Customer
{
??//?Code?here
}
class?NormalCustomer?:?Customer
{
??//?Code?here
}
這樣,如果日后我們發(fā)現(xiàn)業(yè)務(wù)需要,要添加一個InactiveCustomer身份來表示那些很久沒有上來購物的人,我們就可以:
class?InactiveCustomer?:?Customer{
??//?Code?here
??private?const?double?c_Discount?=?1.0;
??public?override?double?Discount
??{
????get?{?return?c_Discount;?}
??}
??public?override?string?ToString()
??{
????return?"Name:?"?+?m_Name?+?"[Inactive]";
??}
}
然而,Order類卻沒有受到任何的影響,這才是最重要的!當(dāng)然,如果因為某些原因,我們決定去掉SuperVip這個身份,也將看到不會對Order產(chǎn)生任何影響!這,就是多態(tài)的威力,剛才發(fā)生在圖書館的一切...忘了吧!
2.1 多態(tài)的種類。
2.1.0 基類繼承多態(tài)(Base Class Polymorphism)
現(xiàn)在讓我們回到網(wǎng)上商店,我們首先創(chuàng)建了一個Customer的abstract class,然后,在Order里面使用這個Customer的“實例”(在Order的構(gòu)造函數(shù)進行賦值初始化)。然而,我們知道一個abstract class是不可能被實例化的,那么我們究竟在引用著什么呢?答案是Customer的派生類的實例(這個派生類就不能夠再是abstract的了)。由于每一個Customer的派生類跟之均是一種is-a的關(guān)系,這些派生類必定能夠執(zhí)行Customer約定的功能,從而只要Order知道Customer提供什么功能,就能夠應(yīng)付這些派生類里同樣的功能了[3]。當(dāng)然,如果我們在派生類里面添加了一些新的功能,那么這些新的功能將不會被Order識別而已,因為Order不能透過Customer的約定來獲知這些新的功能的存在。
使用基類繼承多態(tài)的關(guān)鍵就是有一個繼承體系(如果沒有,就建立一個)[4],而客戶端只需要持有一個類型為這個體系的頂層基類[5]的變量,用于保存其派生類的實例,并調(diào)用預(yù)先約定的抽象方法或者虛方法就行了,剩下的事多態(tài)將會為你好好的安排!
通常我們在面對一組相關(guān)的對象時,我們就會考慮使用多態(tài)。請留意Order.TotalAmount()的代碼片斷:
foreach(Product?product?in?m_Products)??totalAmount?+=?product.Price;
這里的Product可能是一個基類(抽象或者非抽象),它的派生類可能有:Book、Toy、Movie、CD、CPU等等,然而,我們需要一種統(tǒng)一的方法來把一組產(chǎn)品的價格加總。使用多態(tài),就可以避免詢問變量的類型在判斷并讀取所需的數(shù)據(jù)。
2.1.1 接口繼承多態(tài)(Interface Polymorphism)
除了基類繼承多態(tài),我們還有一種接口繼承多態(tài)。顧名思義,這種多態(tài)是通過繼承(更確切的說是“實現(xiàn)”)接口而產(chǎn)生繼承體系的。所以,使用接口繼承多態(tài)的關(guān)鍵也是擁有一個繼承體系。一般情況下我們會盡量使用基類繼承多態(tài)[6],其原因可能是由于關(guān)系表述的準(zhǔn)確性(is-a與can-do之間概念上的區(qū)別[7]),或者版本控制的問題。然而,接口繼承多態(tài)仍然有它獨特的用處,當(dāng)一個對象需要擁有不同的身份時,接口繼承就給了你一種實現(xiàn)的方式。例如String的聲明如下:
public?sealed?class?String?:?IComparable,?ICloneable,?IConvertible,?IEnumerable,?IComparable<string>這樣,String就可以以多種不同的身份出席不同的場合了,例如
System.Collection.SortedList.Add(object?key,?object?value);要求傳遞給該方法的key參數(shù)必須實現(xiàn)IComparable接口,以便能夠進行排序比較。
換句話說,C#不支持基類多重繼承,卻支持接口多重繼承。
2.1.2 混合繼承多態(tài)?
由于一個類可以同時繼承一個基類(base class)、實現(xiàn)多個接口(interface),我們不難想象一下聲明:
public?sealed?class?DerivedClass?:?BaseClass,?IInterface?然而,真的有混合繼承多態(tài)嗎?其實是沒有的,因為在強類型語言里面,變量在給定的某個時刻只能夠以一個身份出現(xiàn),即使它同時具備了多個身份。你不能夠使得一個變量同時是多種類型吧?
2.2 多態(tài)失效了?
使用基類繼承多態(tài),有一點特別需要注意的就是:基類(抽象或者非抽象)中需要獲得多態(tài)效果的成員必須有abstract或virtual修飾。例如:
class?BaseClass{
??//?Code?here
??public?void?PrintMe()
??{
????Console.WriteLine("Printed?in?BaseClass.");
??}
}
class?DerivedClass?:?BaseClass
{
??//?Code?here
??public?void?PrintMe()
??{
????Console.WriteLine("Printed?in?DerivedClass.");
??}
}
class?Program
{
??static?void?Main()
??{
????BaseClass?bc?=?new?BaseClass();
????BaseClass?dc?=?new?DerivedClass();
????bc.PrintMe();
????dc.PrintMe();
??}
}
輸出結(jié)果:
Printed in BaseClass.
Printed in BaseClass.
但是,編譯器將給出以下警告:
The keyword new is required on 'DerivedClass.PrintMe()' because it hides inherited member 'BaseClass.PrintMe()'
當(dāng)我們在BaseClass的PrintMe()前加上virtual時,
public?virtual?void?PrintMe()?將得到如下警告:
'DerivedClass.PrintMe()' hides inherited member 'BaseClass.PrintMe()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
我們不應(yīng)該忽略編譯器的警告,因為別人看你的代碼時,可能會疑惑你的意圖,他(或她)可能猜測你是否漏掉了override或new關(guān)鍵字,又或者在猜測你如果不想繼承基類的成員方法,那為什么要為它起同一個名字呢(尤其是你使用new的時候)?所以你不希望繼承基類的成員方法,那么最好為方法另起一個名字。你可能有一萬個理由表明使用同一個名字的必要,我也沒有絕對不可以這樣做的意思,只是我們應(yīng)該盡最大的努力使的代碼的維護者一眼就能看出代碼的意圖而不是做來回的做揣測。
說得太多了,其實這個是繼承方面的內(nèi)容[8],說這些內(nèi)容主要是希望大家注意程序的輸出結(jié)果。從結(jié)果可以看出,這里并沒有發(fā)生多態(tài)效應(yīng)!所以我有必要為你重復(fù)這一點:
基類(抽象或者非抽象)中需要獲得多態(tài)效果的成員必須有abstract或virtual修飾。
至于使用哪個修飾符就要看你具體的情況了。
然而,使用接口繼承多態(tài)就不需要注意這些了,因為所有的接口均為抽象的,你如果要實現(xiàn)(impletement)一個接口,你就必須實現(xiàn)其所有的成員(無論是顯式還是隱式)[9],否則編譯器將會拒絕編譯而不是僅僅給出警告!
2.3 要睡覺了嗎?
好了,故事講完了,但你總不能先個小孩一樣,聽完故事就上床睡覺吧?我們總得有個事后思考!或許你已經(jīng)發(fā)現(xiàn),從頭到尾我都沒有為多態(tài)下一個明確的定義。是的,我沒有,也不打算這樣做!一個完整精確的定義對我來說難度太大了,所以我選擇用一些例子和相關(guān)的解釋來為你們描述。不過我還是愿意簡單的說一下何謂多態(tài):
多態(tài)就是使得你能夠用一種統(tǒng)一的方式來處理一組各具個性卻同屬一族的不同個體的機制。[10]
關(guān)于這多態(tài),有一點很關(guān)鍵的,那就是多態(tài)是以繼承體系為基礎(chǔ)的,所以上面這句中的“同屬一族”所指的就是“屬于同一繼承體系的”。覺得煩了嗎?還是那一句:忘了它!
?
3. 多態(tài)與重構(gòu)。
3.0 請別妨礙我們改善公司的組織結(jié)構(gòu)!
多態(tài)的威力雖然強大,但并不是所有的代碼一開始就在該用多態(tài)的地方使用多態(tài)。面對既有的代碼,我們?nèi)绾沃匦乱鄳B(tài)的力量呢?答曰:Replace Conditional with Polymorphism![11]
我借用了《重構(gòu)》的一段代碼[12]:
class?Employee{
??//?Code?here
??int?PayAmount()
??{
????switch(GetKind())
????{
??????case?EmployeeType.ENGINEER:
????????return?_monthlySalary;
??????case?EmployeeType.SALESMAN:
????????return?_monthlySalary?+?_commission;
??????case?EmployeeType.MANAGER:
????????return?_monthlySalary?+?_bonus;
??????default:
????????throw?new?InvalidOperationException("Incorrect?Employee");
????}
??}
}
我們希望公司的薪金系統(tǒng)不會妨礙公司組織結(jié)構(gòu)的改變,我們可能有上千個職位,每個都有著不同的薪資算法,這些算法又有可能隨時發(fā)生變化,而且我們還隨時有可能新增一個職位或者去掉一個職位,有或者暫增一個臨時職位。我們希望公司組織結(jié)構(gòu)的改動與薪金系統(tǒng)的改動都同樣靈活!于是,我們要借助多態(tài)的力量了[13]。
3.1 Poly的噩夢——圖書館的倒塌。
某天,Poly發(fā)了一個夢,夢中Poly和Morph坐在圖書館一樓大廳里很休閑的聽著音樂。原來,魔術(shù)榜的效果不但是還書“自動化”,借書也“自動化”了。要借書的話,只需要走進一樓大廳,然后大聲叫出書名,書就會飛到你的手上!如果學(xué)校領(lǐng)導(dǎo)知道這件事的話,肯定嚷著要裁掉他們兩個!此時,一PLMM來到大廳,大叫一聲:今天你多態(tài)了嗎?突然,Poly和Morph還有那PLMM感覺到圖書館在震動,而且越來越厲害,最后,圖書館倒塌了!好在他們?nèi)齻€跑得快,否則后果不堪設(shè)想。究竟出了什么事?突然那PLMM尖叫:那墻上刻有“未處理異常:NullReferenctException”!
真實一個可怕的噩夢,但引用空對象在現(xiàn)實中確實家常便飯之事,你必須額外編寫代碼來處理它[14]:
Employee?e?=?paymentSystem.GetEmployee("Allen");if(e?!=?null?&&?e.IsTimeToPay(today))
??e.Pay();
或者
Employee?e?=?paymentSystem.GetEmployee("Allen");try
{
??if(e.IsTimeToPay(today))
????e.Pay();
}
catch(NullReferenceException)
{
??//?Exception?handler?code
}
然而,這些代碼看起來都不太漂亮,有沒有更統(tǒng)一直觀的處理方式呢?答曰:Introduce Null Object[15]!
class?Employee{
??//?Code?here
??public?virtual?bool?IsTimeToPay(DateTime?dt)
??{
????//?
??}
??public?virtual?void?Pay()
??{
????//?
??}
}
class?NullEmployee?:?Employee
{
??//?Code?here
??public?override?bool?IsTimeToPay(DateTime?dt)
??{
????//?空方法體!
??}
??public?override?void?Pay()
??{
????//?空方法體!
??}
}
class?Program
{
??static?void?Main()
??{
????Employee?e?=?paymentSystem.GetEmployee("Allen");
????//?這樣,如果GetEmployee("Allen");返回一個空引用,下面的代碼將什么也不做!
????if(e.IsTimeToPay(DateTime.Today)
??????e.Pay();
??}
}
實際的操作中,我們?yōu)榱吮苊饪蛻舳藢ullEmployee的了解,可以把該類作為內(nèi)嵌類(Nested class)加入到Employee,并提供工廠方法(Factory Method)來生成NullEmployee的實例。并且,我們會將Null Object模式與Singleton模式結(jié)合一起使用,因為Null Object對于每一個調(diào)用方來說都應(yīng)該是同質(zhì)的,也就是一種常量性質(zhì)的東西,它的成分不應(yīng)該發(fā)生改變。
class?NullEmployee?:?Employee{
??private?static?NullEmployee?m_Instance?=?null;
??private?NullEmployee()?{?}
??public?static?NullEmployee?CreateInstance()
??{
????if?(m_Instance?==?null)
??????m_Instance?=?new?NullEmployee();
????return?m_Instance;
??}
??//?Code?here
}
有時候,我們需要對對象進行類似IsNull()的判斷并讀取里面的值,那么你可以自己聲明一個INullable接口,再讓NullEmployee實現(xiàn)它。然而,你也可以直接實現(xiàn).NET(ver. 2.0)內(nèi)置的System.INullableValue接口:
class?Employee?:?INullableValue{
??//?Code?here
??INullableValue.HasValue()
??{
????return?true;
??}
??INullableValue.Value()
??{
????return?this;
??}
}
class?NullEmployee?:?Employee
{
??//?Code?here
??INullableValue.HasValue()
??{
????return?false;
??}
??INullableValue.Value()
??{
????return?null;
??}
}
然而,Introduce Null Object只能用來處理引用類型(Reference Type),因為值類型(Value Type)都是密封(sealed)的。
?
4. 完結(jié)·新篇[16]。
不知不覺到了結(jié)論部分,我也不知道該說些什么,但總得留下一些東西。好了,回想一下我對“何謂多態(tài)”的回答:
多態(tài)就是使得你能夠用一種統(tǒng)一的方式來處理一組各具個性卻同屬一族的不同個體的機制。
再回想一下我們的這些例子,不難發(fā)現(xiàn),我們一直都在縫縫補補的,我們是修補工嗎?是,也不是。說它是,那是因為我們不能輕易放棄既有的代碼,要為這些代碼做進一步完善;說它不是,那就是如果你一開始就把多態(tài)考慮進你的設(shè)計,那么它就不是一項修補工了,當(dāng)然不排除日后你又需要使用Replace ... with Polymorphism!然而,沒有人能夠一開始就想出一個完美的設(shè)計,所以我們需要重構(gòu),而多態(tài),則為我們提供如何去完善我們的設(shè)計的基本理念。
那么,如果我們一開始就把多態(tài)考慮進我們的設(shè)計又會是怎樣一種情況呢?插件系統(tǒng),如果需要有一點規(guī)模的話,這是我能想到的一個答案。那些支持熱插拔組件的系統(tǒng)(插件系統(tǒng))均支持向系統(tǒng)增加外部功能擴展模塊(插件組件)而無需重新編譯。然而,系統(tǒng)必須能夠識別這些插件組件的功能并執(zhí)行之才有意義。于是,我們會預(yù)先約定一組插件系統(tǒng)能夠識別功能接口,并讓插件組件實現(xiàn)這些接口。這樣插件系統(tǒng)便能識別并以統(tǒng)一的方式來調(diào)用它們。這,不就是多態(tài)嗎?
當(dāng)然,插件系統(tǒng)的設(shè)計與實現(xiàn)是另一個龐大的課題,里面涉及的不僅僅是多態(tài),還有其他很多很多的技術(shù),但多態(tài)肯定是其核心技術(shù),而且,它還必須遵守開放——封閉原則(The Open-Closed Principle,簡稱OCP),這樣,系統(tǒng)才能夠以更靈活的方式去應(yīng)對未來的變化。作為一個開始,我為你們介紹了多態(tài),剩下的路你們就要拿出自己的探索精神了。
?
5. 參考資料。
- Martin Fowler; Refactoring: Improving the Design of Existing Code; Addison Wesley Longman, Inc., 1999
- Robert C. Martin; Agile Software Development: Principles, Patterns, and Practices; Pearson Education, Inc., 2003
- Alan Shalloway, James R. Trott; Design Patterns Explained: A New Perspective on Object-Oriented Design; Addison Wesley, 2002
- Jesse Liberty; Programming C#, 3rd Edition; O'Reilly & Associates, Inc., 2003
- Jeffrey Richter; Applied Microsoft .NET Framework Programming; Microsoft Press, 2002
- Don Box, Chris Sells; Essential .NET, Volume 1: The Common Language Runtime; Addison Wesley, 2002
- Eric Gunnerson; A Programmer's Introduction to C#; Apress, 2000
- Microsoft Corporation; .NET Framework SDK Documentation; Microsoft Corporation
?
X. 注釋。
?
原文鏈接:http://www.cnblogs.com/allenlooplee/archive/2004/11/02/59519.html
轉(zhuǎn)載于:https://www.cnblogs.com/Percy_Lee/p/4863280.html
總結(jié)
以上是生活随笔為你收集整理的今天你多态了吗? 【转】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 提高tomcat的并发能力
- 下一篇: Array原生方法