若是問面向對象的三大特性是什麼,多數人都能回答出來:封裝、繼承、多態。java
繼承 做爲三大特性之一,近來卻愈來愈不推薦使用,更有極端的語言,直接語法中就不支持繼承,例如 Go。這又是爲何呢?設計模式
假設咱們要設計一個關於鳥的類。ide
咱們將「鳥類」定義爲一個抽象類 AbstractBird
。全部更細分的鳥,好比麻雀、鴿子、烏鴉等,都繼承這個抽象類。ui
大部分鳥都會飛,那咱們可不能夠在 AbstractBird
抽象類中,定義一個 Fly()
方法呢?設計
答案是否認的。儘管大部分鳥都會飛,但也有特例,好比鴕鳥就不會飛。鴕鳥繼承具備 Fly()
方法的父類,那鴕鳥就具備「飛」這樣的行爲,這顯然不符合咱們對現實世界中事物的認識。code
解決方案一對象
在鴕鳥這個子類中重寫 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."); } }
這種設計思路雖然能夠解決問題,但不夠優美。由於除了鴕鳥以外,不會飛的鳥還有不少,好比企鵝。對於這些不會飛的鳥來講,咱們都須要重寫 Fly()
方法,拋出異常。接口
這違背了迪米特法則(也叫最少知識原則),暴露不應暴露的接口給外部,增長了類使用過程當中被誤用的機率。開發
解決方案二
經過 AbstractBird
類派生出兩個更加細分的抽象類:會飛的鳥類 AbstractFlyableBird
和不會飛的鳥類 AbstractUnFlyableBird
,讓麻雀、烏鴉這些會飛的鳥都繼承 AbstractFlyableBird
,讓鴕鳥、企鵝這些不會飛的鳥,都繼承 AbstractUnFlyableBird
類。
此時,繼承關係變成了三層,還行得通。
若是要再添加一個游泳 Swim()
的方法,那狀況就複雜了,要分爲四中狀況:
若是再有其餘行爲加入,抽象類的數量就會幾何級數增加。
咱們要搞清楚某個類具備哪些方法、屬性,必須閱讀父類的代碼、父類的父類的代碼……一直追溯到最頂層父類的代碼。
針對「會飛」這樣一個行爲特性,咱們能夠定義一個 Flyable
接口,只讓會飛的鳥去實現這個接口。針對會游泳,定義一個 Swimable
接口,會叫定義一個 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("!@#$%^&*……"); }
麻雀和企鵝都會叫,Tweet
實現了兩遍,這是壞味道。咱們能夠用組合來消除這個壞味道。
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(); }
雖然如今主流的思想都是多用組合少用繼承,可是從上面的例子能夠看出,繼承改寫成組合意味着要作更細粒度的類的拆分,要定義更多的類和接口。類和接口的增多也就或多或少地增長代碼的複雜程度和維護成本。因此,在實際的項目開發中,咱們仍是要根據具體的狀況,來具體選擇該用繼承仍是組合。
本文出自極客時間 王爭 老師的課程《設計模式之美》。原文示例爲 java,由於我是作 C# 的,因此本文示例代碼我改爲了 C# 。