本節書摘來自異步社群《面向對象設計實踐指南:ruby語言描述》一書中的第8章,第8.1節組合對象,作者【美】sandi metz,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
第8章 組合對象
面向對象設計實踐指南:ruby語言描述
組合(composition)是指将不同的部分結合成一個複雜整體的行為,這樣整體會變得比單個部分的總和還要大。例如,音樂就是組合而成的。
你可不能将軟體當作是音樂,那隻是一種類比。貝多芬的第五交響曲樂譜是一長串獨特而又獨立的記号。你隻聽一遍就會明白:盡管它包含的是一些記号,但它不是記号。它是另一回事。
你可以按同樣的方式來建立軟體,使用面向對象的組合技術來将簡單、獨立的對象組合成更大、更複雜的整體。在組合過程中,較大的那個對象通過“有一個”關系與其部分相連。一輛自行車有多個零件。自行車就是那個包含對象,而零件則被包含在自行車裡。“組合”定義的中心思想是:自行車不僅有多個零件,它還要通過接口與它們進行通信。零件是一個角色,而自行車很樂意與任何扮演這個角色的對象進行合作。
本章會對oo組合技術進行講解。在開始時舉了一個示例,接着會對組合與繼承的相對優缺點進行讨論,然後得出如何選擇替代設計技術的建議作為結論。
8.1 parts組合成bicycle
本節接着從第6章結尾那個bicycle示例開始。如果你對那段代碼已沒了印象,那麼請翻回到第6章的末尾,并溫習一下。本節會利用這個示例,通過多次重構來推動它的更新,同時逐漸地使用組合來取代繼承。
8.1.1 更新bicycle類
在繼承層次結構裡,bicycle類目前還是一個抽象父類。現在,想要将它轉換來使用組合技術。第一步是忘掉現有的代碼,然後好好想想應該如何組合出一輛自行車。
bicycle類負責響應spares消息。這條資訊應該傳回一個備件清單。自行車有多個零件,是以“自行車—零件”關系很自然會讓人感覺像組合。如果你建立了一個用于容納所有自行車零件的對象,那麼你就可以将備件資訊委托給這個新對象。
将這個新類命名為parts非常合理。parts對象可以負責容納自行車零件清單,并負責了解哪些零件需要備件。請注意,這個對象代表了一堆的零件,而不是單個零件。
圖8-1裡的那張時序圖展示了這種思想。其中,bicycle向它的parts對象發送了spares消息。
每一個bicycle都需要一個parts對象。所謂零件,其意思是說,bicycle有一個parts。圖8-2裡的那張圖展示了這種關系。

這張圖所展示的是:bicycle與parts類通過一根線連接配接在一起。這根線的黑色菱形那一端連接配接的是bicycle。黑色菱形表示的是“組合”(composition),即它意味着bicycle由parts組合而成。在這根線的parts那端有數字“1”。它表示的是每一個bicycle都隻有一個parts對象。
将已有的bicycle類轉換成這種新的設計比較容易。删除其大部分代碼,添加一個parts變量用于儲存parts對象,并将spares委托給parts。下面是新的bicycle類:
bicycle現在要負責三件事情:知道其size、儲存parts并回答spares。
8.1.2 建立parts層次結構
上面那個很容易做到,不過那隻是因為:在開始的時候,bicycle類裡并沒有太多與自行車相關的行為(bicycle的大部分代碼都在處理parts)。你仍然需要那些剛從bicycle裡移除的parts行為。而讓這段代碼可以再次工作的最簡單方法,就是簡單地将這些代碼轉移到一個新的parts層次結構,如下所示。
這段代碼是幾乎就是第6章的那個層次結構的翻版,不同之處在于類的名字被改了,并且删除了size變量。
圖8-3裡的類圖展示了這種轉變。現在有一個抽象的parts類。bicycle由parts組合。parts有兩個子類:roadbikeparts和mountainbikeparts。
在這個重構之後,所有事情都還可以正常地工作。如下面所示,不管它擁有的是roadbikeparts還是mountainbikeparts,bicycle對象都可以正确地回答出其size和spares。
這個變化不大,并且沒做大的改進。不過,這種重構揭示了一件很有用的事情,即,很明顯,在開始時與bicycle有關的代碼非常少。上面的大部分代碼都在處理單個零件,那個parts層次結構現在迫切需要進行另外的重構。
本文僅用于學習和交流目的,不代表異步社群觀點。非商業轉載請注明作譯者、出處,并保留本文的原始連結。