當原本應該集中在同一抽象的資料與方法被散落到多個抽象時,就出現 Broken Modularization。

核心定義#

最常見的兩種形態:

  • 純資料容器類別——只有資料、沒有操作。
  • 方法住在 A 類別,但所有動作對象都在 B 類別

「資料與操作應該在語意上隸屬的同一個類別中和諧協作。」—— Lanza & Marinescu

違反「區域化相關的資料與方法」即引發此壞味道。

常見成因#

  • OO 中的程序式思維:習慣 C 的 struct、Pascal 的 record,把資料與函式分離。
  • 缺乏對既有設計的認識:大型專案中開發者只看到一小角,不知道方法該擺哪裡。

範例#

範例 1:Device / DeviceData#

設備管理應用中:

  • DeviceData:只含 public 資料欄位、零方法。
  • Device:持有 DeviceData 物件,提供存取與操作。

兩者該是同一抽象卻被分割——典型的 Broken Modularization。

Figure 5.4: 設備管理應用中的類別圖(範例 1)

範例 2:Order 類別只擺資料#

電商應用的 Order 只有資料欄位,驗證、稅金、狀態變更等程式碼散落整個應用:

Figure 5.5: 只含 public 資料成員的 Order 類別(範例 2)

後果:

  • 改動需動多處。
  • 欄位 public → 客戶端可繞過合法的狀態流程,直接從 new 跳到 closed

軼事:跨層的 check 功能#

一個多版本設備設定工具:v1.0 的 check 在 UI 層;v1.2 加在 Utility 層;v2.0 連 Business Logic 層也有。沒有任何重構介入。最終 check 散布三層,每次修改都得同步檢視全部。

教訓:小決策也會層層放大區域化、關注點分離、高內聚低耦合從一開始就要堅持。

建議重構#

套用「convert procedural design to objects」重構:把資料與其行為合到同一類別中。

  • 方法在 A 類別中卻更頻繁地操作 B 類別的成員 → move method
  • 欄位在 A 類別中卻被 B 類別大量使用 → move field

Figure 5.6: 針對 Broken Modularization 壞味道的建議重構

範例 1 的重構#

合併 DeviceDataDevice 為單一類別。

Figure 5.7: 設備管理應用的建議重構

範例 2 的重構#

  • Order 拆為 OrderItemOrderDetailPayment
  • 將欄位改為 private。
  • validateOrder() 等方法移回 Order
  • 不屬於 Order 的成員移到合適的類別。

重構時要注意分寸——把「看似相關」的方法全塞進一個類別,反而會引發 Multifaceted Abstraction(3.4 節)或 Insufficient Modularization(5.2 節)。

對品質屬性的衝擊#

  • 理解性:要看懂一個功能必須跨多個類別追蹤資料與方法。
  • 可變更性與可擴充性:每次變更都需要動多個類別。
  • 可重用性與可測試性:無法以單一抽象重用、測試。
  • 可靠性:方法被擺錯地方時,會接觸到不該接觸的內部資料結構或演算法。

別名#

  • Class passively stores data:只儲存資料、沒有操作方法。
  • Data class:只是「啞」資料容器,但被大量類別倚賴。
  • Data recordsRecord (class):只有欄位、沒有方法(Pascal record 風格)。
  • Data container:類別只負責放資料,由另一個類別操作。
  • Misplaced operations:應在內部的內聚操作放到了類別外。
  • Feature envy:方法對另一個類別的成員「更感興趣」。
  • Misplaced control:功能未經正當理由就與資料分離。

實務考量#

自動產生的程式碼#

由模型自動產生的程式碼常含資料類別。直接修改會導致模型與程式碼不同步——接受這類資料類別作為自動產生的副產品較合適。

Data Transfer Objects (DTOs)#

DTO 用於跨程序傳輸資料、減少遠端呼叫次數。有意聚合資料、不含行為——以利序列化。這不是壞味道,是設計選擇。