---
title: "組合和繼承"
date: "2026-03"
updated: "2026-09"
category: "設計模式與架構"
tags: ["設計模式與架構"]
description: "組合優於繼承的關鍵因素是去耦合，或者説，就像積木一樣的模組，且應當盡可能的消除外部的狀態和副作用。理想的組合，彼此應當是原子的。只對自身的內部負責，而不關心外部..."
source: "https://enriquemark.com/zh-hant/posts/composition-and-inheritance/"
---

組合優於繼承的關鍵因素是去耦合，或者説，就像積木一樣的模組，且應當盡可能的消除外部的狀態和副作用。理想的組合，彼此應當是原子的。只對自身的內部負責，而不關心外部。但類就不一樣了，繼承本身就暗示了相反的東西，它的關鍵點在於「狀態和行為的共享」——這會造成無形間的耦合，這是代價，但在某些時候確實是需要的。這裡我們可以從何時用組合看出來。什麼時候用它？[組合](/zh-hant/posts/composition/)裡面提到「有一個」和「是一個」的區別，類本身是字面意義上的「一類」，換言之，他們是一個物種——共享結構上固定的（就像上面所説的）、提前定義好的父類狀態（屬性）和方法（實現的具體行為）。

而我們所説的耦合，指的就是這種狀態共享上帶來的隱藏關聯。父類和子類共享一個可變的狀態，但它對下面的繼承者擴展了什麼是無知的，換言之它無法預料子類到底依賴了自己的哪個部分，也許你更改了一個無關緊要的東西，但遠在天邊的另一個功能壞了；或者反過來，任何一個子類改了祖父類的狀態，使用這個狀態的他自己的父類就可能出問題，等等諸如此類——這就是隱式依賴，順著整個鏈條傳導的後果。

正常來説哪怕是擴展，用的時候都不該出問題，如果約定良好，確保繼承時的紀律和測試覆蓋，的確可以讓繼承鏈出問題時不至於出生產事故。但是，我為什麼非要搞得這麼耦合呢？萬一子類就是胡搞，導致方法直接出錯，或者搞了非預期的副作用，只要你是關聯的，那就還是會出問題。畢竟在團隊開發中，每個人都只能看到自己的一個部分，甚至人員流動之下，前後都是彼此不共享資訊的。撰寫父類的人不可能窮盡一切，只要開了擴展點，那什麼牛鬼蛇神都可能出來。

如果我需要專注於狀態和行為的具體實現，且希望後續的依賴方「繼承」（字面意義上）這些內容，那這種情況下用基類和繼承的模式是標準做法，沒什麼冗餘的。畢竟我不能讓人類當一個佛蘭肯斯坦，用一大堆不相干的「積木」去拼湊，就算這樣最終能跑起來，它也會像科學怪人那樣是讓人困惑的——雖然就像上述説的一樣，能動，但我們的印象就是人類有其固有的狀態，這些狀態必須是繼承的而不是拼湊的，人類必須得是「是一個」，所以根本不是標準做法了，而是成了「反模式」。

但有些情況下我們就需要組合：積木、去耦合化的模組。它面向的是「有一個」。我只是想要一個復用的功能，就跟樂高一樣，可以在任何地方拼，也不依賴什麼必然的固定狀態，那組合就再合適不過了。你拼成房子還是直升機我不感興趣，只要你能拼，那你就能用。

而對組合來説，至關重要的一個東西就是「介面」。何為介面？如果説類是專注於狀態和行為的具體實現，那麼介面就是專注於結構本身的約束——它不關心實現，而只關心這個實現應該是什麼形狀的。

從另外一個方面說，組合併不與類排斥。因為組合的靈活性，它的拼裝場所可以發生在任何地方——例如類的裡面。即便是類，我也沒必要把各種工具方法都作為這個類的固有行為塞進去，那隻會造成過於臃腫的方法膨脹。需要的時候直接使用組合工具，為什麼不呢？只要組合能夠確保自己的輸入和輸出是不變的，那麼它對任何使用方也都是明確的。
