物件導向程式設計另一個十分常見的問題是「過大的類別(Large Class)」。何謂「過大的類別」?從表面上來看,過大的類別如果不是內含過多的資料成員,就是具備太多的Methods。而這二者有時是互為因果的。
高度相依容易演變成牽一髮而動全身的局面
由於物件導向設計強調擴充性,在設計一個類別時,起初多半不會賦予它重大的任務,所以每個類別在設計之初,時常都符合輕薄短小的標準。但隨著時間過去,基於需求或設計的新增變更,導致程式人開始為某些類別添加新功能,而且擴展規模的類別,往往還有集中化的趨勢。
也就是說,會持續添加新功能的類別,往往就是那幾個。於是強者愈強,這幾個類別就像磁鐵一樣,不斷地吸附上新的功能實作。在類別的設計中,當一個類別新增了一個Method,往往代表著它對客戶端程式碼多提供了一項的服務,也同時意謂它的責任又增加了一項。
從責任的角度檢視類別所扮演的角色,是很有用的一種手法。早期的物件導向設計方法論CRC(Class-Responsibility-Collaboration),便將類別的責任視為設計類別時重要的考量。
一個採用物件導向方法設計而成的系統,可以被看做是由各個大小不一的類別物件所構築而成的世界,這些物件各有責任與角色,憑藉著彼此之間的相互合作,達成系統所提供的各種目的。
倘若系統中存在責任過重的類別(可稱之為「Large Class」),往往意謂著系統中會有更多的類別,倚靠這個Large Class所提供的服務,相依於此Large Class的其他類別數量就愈多,使得這個Large Class成為被高度相依的類別。
高度的相依並不是一件好事,因為這容易演變成為牽一髮而動全身的局面。當這個Large Class本身有了變化時,由於相依於此的類別數量繁多,使得可能影響到的類別也就變多了。這當然不是一件好事,雖然變化在所難免,但每次發生變化時,所波及的範圍愈小愈好。
複雜度提高,維護更困難
此外,責任過重的類別,內部的實作複雜度往往也高。一個責任較重的類別,會有較多的Public Methods,為了達成這些Public Methods的功用,往往需要較多的Private Methods。
Private Methods代表的是類別的內部實作,當類別具備較多的Private Methods時,內部實作複雜度必高。因為不僅是在Public Methods呼叫Private Methods時會產生複雜度,更多的複雜度是來自於Private Methods之間的相互呼叫。高度複雜的類別,自然更難以令人理解,因而增加了維護及修改時的難度。
我曾見過超過一萬行的Super Large Class程式,這種規模固然不常見,三至五千行的Large Class卻屬稀鬆平常。這麼大規模的Class,固然很難讓程式原作者之外的人輕易讀懂,即便是原作者,在修改或加強此類別時,恐怕也不容易很快找到需要修改或調整的地方。
更何況,此類的類別內聚力強,在需要修改時,也會在內部引發連鎖反應(修改一處,引發更多處的修改),造成維護上的困難。
肥大的類別,會越吃越肥
此外,過於龐大的類別和過度冗長的Method相似,往往意謂著此類別中尚有某一些Methods可以拆解出來成為獨立的類別。這會影響到未來重複使用的機會。所以過於龐大的類別也和過度冗長的method一樣,會與程式碼重複的問題相伴出現。
更嚴重的是,類別中所含的大量Methods,倘若應被拆解但未被拆解,當其他類別對於這些Methods有所需求時,設計者往往會選擇讓這些類別成為Large Class的「客戶」,也就是說,讓它們繼續使用Large Class。
貪圖便利的設計者,更會傾向於把這些Large Class的客戶端類別的其他需求,也通通堆到這個Large Class中,以避免產生新的類別。
這麼一來,就會形成之前所提的:強者愈強,而且Large Class會像磁鐵一樣,不斷把新增的功能實作,往自己身上吸。這是之所以大型的類別很常見的原因。
透過提煉類別的手法,重新分配責任
當設計者查覺自己的系統中,漸漸形成了Large Class,可以考慮採用重構技巧中的「提煉類別(Extract Class)」手法解決問題。基本上,「提煉類別」 是一種責任重新分配的動作。在進行「提煉類別」時,設計者需要重新全盤檢視Large Class目前所擔負的責任。設計者需要思考:有哪些工作,可以創造出一個更恰如其份的類別,為這個Large Class分勞解憂。
從程式碼的角度來看,程式設計者可以檢視所有的Methods當中,是否存在某一組Methods,它們彼此之間的相關度較高(內聚力高),但和其餘Methods的相關度較低,甚至不具相關度(低耦合力)。
倘若存在這樣一組Methods,便暗示著設計者:這一組Methods有很高的機會適合抽離出來,成為一個單獨的類別。
當設計者利用「提煉類別」重新分配類別之間的責任,各個類別之間的責任負擔將趨於均衡,沒有過強的類別,也沒有過弱的類別(過弱的類別是重構方法中的另一個問題)。
一個責任分配均衡的物件導向系統,通常都是比較健康的系統。因為每個類別的大小適宜,因而使得每個類別都容易理解,也因為容易理解,就容易維護。大小適宜的類別,容易重複運用,因為它們所表示的語義單元不會太大,使得它們被其他程式碼運用的可能性提高,這就是殺雞不用牛刀的道理,其他類別倘若只是想殺雞,通常不會選擇一把威力強大的牛刀,只會挑一把用途剛好的雞刀。
大小適宜的類別,因為責任也不會太多,所以不會被太多的類別倚靠,當這個類別被修改甚至是移除時,不會影響到太多的類別,這使系統更能抵抗各類變更所帶來的影響。
非誰不可,就大有問題
物件的世界也和真實的世界相仿。在一個真實世界中,好的組織會由責任分工明確的體系主導運作。在這樣的體系中,每個成員都只是龐大機器中的一小部份。每個成員只負責一小塊責任範圍,但也極其忠誠地扮演自己被賦予的角色。
這樣的一個分工體系,不會存在能力(責任)過大的成員,即便是任何一個成員被抽離,都不致於對既有的組織產生極大的影響。一個不好的組織,時常存在能力(責任)過大的(核心)成員,這使得整個組織的運作,核心成員非存在不可。
但核心成員有所變化或消失時,整個組織的機制便大幅受創,甚至無法繼續運作下去。我們固然可以從真實的世界體會物件世界運作的原理,但這樣一對照,又未嘗不能從物件世界運作的現象,回頭深刻的體會真實世界呢?
《作者簡介》王建興
清華大學資訊工程系的博士研究生,研究興趣包括電腦網路、點對點網路、分散式網路管理、以及行動式代理人,專長則是Internet應用系統的開發。曾參與過的開發專案性質十分廣泛而且不同,從ERP、PC Game到P2P網路電話都在他的涉獵範圍之內。
相關閱讀:
物件導向程式設計常見的錯誤(1)抽離作用重複的程式碼,重構品質
物件導向程式設計常見的錯誤(3)立體的系統架構,可降低修改的影響
物件導向程式設計常見的錯誤(4)Façade畫分子系統,可加速開發
物件導向程式設計常見的錯誤(5)工程化的考量不可走火入魔
熱門新聞
2025-01-13
2025-01-15
2025-01-14
2025-01-14
2025-01-13