問題:
每當構思我的程式時,我經常會有以下一連串的思考:
一個足球隊是一群足球運動員的集合,因此,我可以用下面的程式碼來宣告:
1 |
var football_team = new List<FootballPlayer>(); |
這個list的排序順序就是這些運動員在花名冊裡的順序。但是之後我又意識到除了運動員外,球隊還有別的屬性也需要記錄。比如說這個賽季的總得分,當前的預算,球服的顏色,用string型別表示的球隊名字,等等。
所以我認為:
既然一個球隊就是一群運動員的集合,但是額外的,它有一個名字(string型別)和一個當前總得分(int型別)。 .NET沒有提供一個用來儲存球隊的類,所以我得自己定義一個類。和現有結構最接近的是List<FootballPlayer>,所以我將繼承它:
1 2 3 4 5 |
class FootballTeam : List<FootballPlayer> { public string TeamName; public int RunningTotal } |
但是結果是,我被提示不能繼承List<T>。 關於這個提示,我有兩點疑惑:
為什麼不能?
顯然List某種程度是對效能的優化,怎麼做到的?如果我繼承了List對效能會有什麼影響?到底帶來了什麼?
另外,List是微軟提供的,我不能控制它,當它對外界表徵為“public API”我就不能修改它。但是我很難理解什麼是Public API,為什麼我要關心它?如果我的專案不需要或者根本沒有這樣的public API,我是不是就可以無視那個提示?如果我不繼承List那麼我便需要一個Public API,我該怎麼解決?為什麼它這麼重要呢,一個list就是個list,有什麼可能改變的呢?我可能想要改變什麼呢?
最後,如果微軟不希望我繼承List,為什麼不給List類加sealed修飾符呢?
除此之外我該使用什麼?
顯然,對於自定義的集合,微軟提供了可以繼承的Collection類來取代List。但是這個類功能很少,沒有提供很多有用的東西,比如說AddRange。
繼承Collection比繼承List要多做很多活,而且我找不到優點。微軟一定不會告訴我去做額外的活,所以我覺得某種程度上我誤解了什麼,而且實際上對於我的問題繼承Collection也不是一個好的解決方案。
我也看到一些解決方案,比如實現IList。但是我不會這麼做,幾十行的範例程式碼讓我覺得什麼用處也沒有。
最終,一些人建議我這麼使用List:
1 2 3 4 |
class FootballTeam { public List<FootballPlayer> Players; } |
但是這麼做有兩個問題:
1. 它使我的程式碼變得不必要的冗長。我一定要呼叫my_team.Players.Count而不是my_team.Count。感激的是,用C#我可以定義索引器來建立索引,然後傳送List中的所有內部方法。但是我要寫很多程式碼。做那麼多的活我得到了什麼?
2. 這只是抱怨沒有別的意思。一個足球隊不是包含一組運動員,它就是一組運動員。你不會說John McFootballer已經加入了某個球隊的運動員集合裡,你會說John McFootballer加入了某個球隊。你不會說把一個字母加入到一串字串的字母集合裡,你會說把一個字元加入一串字串裡。你不會說把一本書放到一個圖書館的圖書集合裡,你會說把一本書放到一個圖書館裡。
我開始意識到為什麼會這樣了,其實可以說成“把x新增到y的內部集合裡”,但是這樣的方式思考世界似乎是違背常理的。
我的問題(總結)
用C#表達邏輯(也就是說,從人類的思考方式)是list之類的帶有鐘聲和口哨的資料結構,正確的方法是什麼?
是不是繼承List<T>一直不會被接受?或者說什麼時候可以?為什麼能或不能?當決定是否繼承List<T>,一個程式設計師需要思考什麼?
最佳答案:(來自 Eric Lippert,他是微軟的一名資深軟體設計工程師,之前伯樂線上翻譯過他的一些博文)
已經有很多很好的答案,我很樂意在他們的基礎上補充幾點。
用C#表達邏輯(也就說說,從人類的思考方式)是list之類的帶有鐘聲和口哨的資料結構,正確的方法是什麼?
讓任意十個非計算機程式設計師並且熟悉足球的人來填寫下面這句話的空白處:
一個足球隊是一種特定的__________
有任何一個人寫“帶有鐘聲和口哨的運動員集合”嗎,或者還是都寫“運動隊”、“俱樂部”、“組織”?足球隊是一種特定的運動員集合這個概念只是你大腦中的想法並且也只是你而已。
List<T>是一種機制。足球隊是一種商業物件——也就是說,在程式中,這種物件是在業務邏輯領域中的一種概念。不要混淆了這些。足球隊就是一個團隊,它有一個花名冊,花名冊是一組運動員集合。它就是運動員的集合。所以定義一個屬性叫Roster表示List<Player>,並且當你使用時,定義成
1 |
ReadOnly List<Player> |
除非你認為每個人都可以得到球隊的花名冊並且可以從中刪除隊員。
是不是繼承List<T>一直不會被接受?
不能被誰接受?我嗎?不是。
什麼時候可以?
當你需要建立一種需要繼承List<T>機制的機制的時候。
當決定是否繼承List<T>,一個程式設計師需要思考什麼?
我是在建立一種機制還是建立一個業務物件?
我要寫很多程式碼。做那麼多的活我得到了什麼?
你花了大量的時間把你的問題敲打出來,這個時間已經夠你把與List<T>相關聯的成員方法寫上50遍以上了。顯然你是不怕冗長的,我們在這裡討論的只是幾行程式碼,這是幾分鐘的工作。
更新
我又思考了一下,還有一個理由來說明足球隊不是運動員的集合。事實上,覺得足球隊包含一個運動員集合也許也是一個不對的想法。不管是球隊是運動員集合還是有一個運動員集合,問題是你得到的只是球隊某個時刻的一個快照,我不知道你定義這個類的業務情景式什麼,但是如果讓我來定義一個表示足球隊的類,我會先問自己,“多少西雅圖海鷹隊的隊員由於傷病錯過了2003~2013年之間的比賽 ?”或“哪一個先前在另一個隊效力的丹佛隊運動員擁有在碼跑中的最高年增長率?”或“今年Piggers 還會一路走好嗎?”
也就是說,對我來說一個足球隊就是一個對球員們僱傭、傷病、退役等生涯進行記錄的記錄集。很明顯,當前的花名冊就是一個重要的記錄,所以應該是首先要考慮的,但是對足球隊這個物件來說,也許你有更多別的你感興趣東西,但那需要有更高的歷史視角。