c# 如何用組合替代繼承
如果問面向?qū)ο蟮娜筇匦允鞘裁矗鄶?shù)人都能回答出來:封裝、繼承、多態(tài)。
繼承 作為三大特性之一,近來卻越來越不推薦使用,更有極端的語言,直接語法中就不支持繼承,例如 Go。這又是為什么呢?
為什么不推薦使用繼承?
假設(shè)我們要設(shè)計(jì)一個(gè)關(guān)于鳥的類。
我們將“鳥類”定義為一個(gè)抽象類 AbstractBird
。所有更細(xì)分的鳥,比如麻雀、鴿子、烏鴉等,都繼承這個(gè)抽象類。
大部分鳥都會(huì)飛,那我們可不可以在 AbstractBird
抽象類中,定義一個(gè) Fly()
方法呢?
答案是否定的。盡管大部分鳥都會(huì)飛,但也有特例,比如鴕鳥就不會(huì)飛。鴕鳥繼承具有 Fly()
方法的父類,那鴕鳥就具有“飛”這樣的行為,這顯然不符合我們對(duì)現(xiàn)實(shí)世界中事物的認(rèn)識(shí)。
解決方案一
在鴕鳥這個(gè)子類中重寫 Fly()
方法,讓它拋出異常。
public class AbstractBird { public virtual void Fly() { Console.WriteLine("I'm flying."); } } //鴕鳥 public class Ostrich : AbstractBird { public override void Fly() { throw new NotImplementedException("I can't fly."); } }
這種設(shè)計(jì)思路雖然可以解決問題,但不夠優(yōu)美。因?yàn)槌锁r鳥之外,不會(huì)飛的鳥還有很多,比如企鵝。對(duì)于這些不會(huì)飛的鳥來說,我們都需要重寫 Fly()
方法,拋出異常。
這違背了迪米特法則(也叫最少知識(shí)原則),暴露不該暴露的接口給外部,增加了類使用過程中被誤用的概率。
解決方案二
通過 AbstractBird
類派生出兩個(gè)更加細(xì)分的抽象類:會(huì)飛的鳥類 AbstractFlyableBird
和不會(huì)飛的鳥類 AbstractUnFlyableBird
,讓麻雀、烏鴉這些會(huì)飛的鳥都繼承 AbstractFlyableBird
,讓鴕鳥、企鵝這些不會(huì)飛的鳥,都繼承 AbstractUnFlyableBird
類。
此時(shí),繼承關(guān)系變成了三層,還行得通。
如果要再添加一個(gè)游泳 Swim()
的方法,那情況就復(fù)雜了,要分為四中情況:
- 會(huì)飛會(huì)游泳
- 會(huì)飛不會(huì)游泳
- 不會(huì)飛會(huì)游泳
- 不會(huì)飛不會(huì)游泳
如果再有其他行為加入,抽象類的數(shù)量就會(huì)幾何級(jí)數(shù)增長。
我們要搞清楚某個(gè)類具有哪些方法、屬性,必須閱讀父類的代碼、父類的父類的代碼……一直追溯到最頂層父類的代碼。
使用組合
針對(duì)“會(huì)飛”這樣一個(gè)行為特性,我們可以定義一個(gè) Flyable
接口,只讓會(huì)飛的鳥去實(shí)現(xiàn)這個(gè)接口。針對(duì)會(huì)游泳,定義一個(gè) Swimable
接口,會(huì)叫定義一個(gè) Tweetable
接口。
public interface Flyable { void Fly(); } public interface Swimable { void Swim(); } public interface Tweetable { void Tweet(); } //麻雀 public class Sparrow : Flyable, Tweetable { public void Fly() => Console.WriteLine("I am flying."); public void Tweet() => Console.WriteLine("!@#$%^&*……"); } //企鵝 public class Penguin : Swimable, Tweetable { public void Swim() => Console.WriteLine("I am swimming."); public void Tweet() => Console.WriteLine("!@#$%^&*……"); }
麻雀和企鵝都會(huì)叫,Tweet
實(shí)現(xiàn)了兩遍,這是壞味道。我們可以用組合來消除這個(gè)壞味道。
public interface Flyable { void Fly(); } public interface Swimable { void Swim(); } public interface Tweetable { void Tweet(); } public class FlyAbility : Flyable { public void Fly() => Console.WriteLine("I am flying."); } public class SwimAbility : Swimable { public void Swim() => Console.WriteLine("I am swimming."); } public class TweetAbility : Tweetable { public void Tweet() => Console.WriteLine("!@#$%^&*……"); } //麻雀 public class Sparrow : Flyable, Tweetable { FlyAbility flyAbility = new FlyAbility(); TweetAbility tweetAbility = new TweetAbility(); public void Fly() => flyAbility.Fly(); public void Tweet() => tweetAbility.Tweet(); } //企鵝 public class Penguin : Swimable, Tweetable { SwimAbility swimAbility = new SwimAbility(); TweetAbility tweetAbility = new TweetAbility(); public void Swim() => swimAbility.Swim(); public void Tweet() => tweetAbility.Tweet(); }
雖然現(xiàn)在主流的思想都是多用組合少用繼承,但是從上面的例子可以看出,繼承改寫成組合意味著要做更細(xì)粒度的類的拆分,要定義更多的類和接口。類和接口的增多也就或多或少地增加代碼的復(fù)雜程度和維護(hù)成本。所以,在實(shí)際的項(xiàng)目開發(fā)中,我們還是要根據(jù)具體的情況,來具體選擇該用繼承還是組合。
以上就是c# 如何用組合替代繼承的詳細(xì)內(nèi)容,更多關(guān)于c# 組合替代繼承的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#中的multipart/form-data提交文件和參數(shù)
這篇文章主要介紹了C#中的multipart/form-data提交文件和參數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06C#實(shí)現(xiàn)簡易計(jì)算器功能(2)(窗體應(yīng)用)
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)簡易計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01解析C#設(shè)計(jì)模式編程中外觀模式Facade Pattern的應(yīng)用
這篇文章主要介紹了C#設(shè)計(jì)模式編程中外觀模式Facade Pattern的應(yīng)用,外觀模式中分為門面(Facade)和子系統(tǒng)(subsystem)兩個(gè)角色來進(jìn)行實(shí)現(xiàn),需要的朋友可以參考下2016-02-02將DLL放入到資源中,運(yùn)行時(shí)自動(dòng)加載的小例子
這篇文章介紹了將DLL放入到資源中,運(yùn)行時(shí)自動(dòng)加載的小例子,有需要的朋友可以參考一下2013-10-10深入多線程之:Reader與Write Locks(讀寫鎖)的使用詳解
本篇文章是對(duì)Reader與Write Locks(讀寫鎖)的使用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05