[設計案例] 生命遊戲 #6, 抽像化 (Abstraction)

[設計案例] 生命遊戲 #6, 抽像化 (Abstraction)

摘要提示

  • 抽像化: 以一般化概念隔離細節,讓互動只經由共同介面/基底類別進行
  • 物件導向關係: 透過繼承與介面,分離「世界」與「生命」的互動契約與具體實作
  • Life/World/Cell 架構: 以 Life 為上層抽像,Cell 為特化,World 只依賴 Life
  • 一般化與特殊化: 共同特性上移至 Life,差異留在 Cell 等衍生類別中
  • 動態聯結: 主程式不改動也能載入新規則/生物,執行期依子類別行為運作
  • 生命遊戲擴充: 在原四條規則外加入「感染」機制以驗證抽像化彈性
  • 顯示與狀態: DisplayText 以符號呈現生/死/感染,狀態由 WholeLife 推進
  • 規則實作: 以鄰居計數與機率運算實現存活、感染、痊癒與死亡
  • 主程式角色: GameHost 只建立 Cell 並放入 World,其餘全由抽像互動驅動
  • 過度設計反思: 抽像化為變化留彈性,但避免預知未知需求造成不必要複雜度

全文重點

本文以「抽像化」為主軸,說明如何用物件導向的繼承與介面,將系統中會變與不會變的部分適當分離。作者從生活比喻切入:開車的能力是對「車」的抽像理解,能在未來新車上延續使用;反觀軟體若缺乏抽像化,舊版程式難以處理未來格式或功能。回到生命遊戲,作者將「世界與生命如何互動」定為抽像核心,建立 World 與 Life 的契約,並以 Cell 作為 Life 的特化。Life 保存所有生命體的共同特質(所屬世界、座標、顯示方式、生命推進方法等),World 一律以 Life 的抽像來操作,避免耦合到任何具體生命體。

設計上採一般化(generalization)與特殊化(specialization):共通行為上移至 Life,差異由 Cell 等子類別實作。接著,為驗證抽像化的韌性,作者將生命遊戲原有的四條規則加入「感染」機制:正常細胞有機率被感染、感染會在三次狀態後痊癒、感染期間有死亡機率。畫面以符號區分正常活、感染、死亡的狀態。程式層面,Cell 以屬性 InfectedCount 判定感染狀態,DisplayText 依三態輸出不同符號;WholeLife 迴圈中先計算鄰居數與感染數,依表格決定生死,然後處理感染遞減、死亡機率或感染觸發,並以隨機時間推進。

關鍵在於 World 完全不需知道 Cell 的細節,主程式只負責建立 Cell 並放入 World,其餘皆透過 Life 的抽像互動與動態聯結在執行期運作。這證明在不修改 World/Life 設計的前提下,可以持續擴充規則甚至替換生物型態(下一篇將以草、羊、虎生態系驗證)。最後,作者提醒避免過度設計:為未知需求預留過多機制,往往增加複雜度與成本;正確作法是以穩健的抽像化隔離變化,在需求成形時再以特化擴充,兼顧彈性與開發效率。

段落重點

抽像化的直覺比喻與必要性

以「開車」比喻抽像化:人們掌握的是對車的共同概念(方向盤、油門、煞車),而非品牌或款式細節,因此能在新車上延用能力。反觀軟體若缺抽像化,舊版軟體難處理未來變化(如早期 Word 無法開啟後期檔案)。差異在於抽像層的有無:若互動建立在抽像層,未來變化可被吸納。物件導向藉由繼承與介面嘗試建立這種隔離層,讓互動經由共同契約進行,細節藏於具體實作中。

生命遊戲中的抽像核心:World 與 Life 的契約

釐清問題域:世界是 M×N 棋盤,每格可容納一個生物,生物有狀態並隨時間與環境變化。設計上以 Life 表達所有生命的共性,Cell 作為其特化;World 與生命的互動以 Life 的抽像為依據。以 class diagram 的構想說明:World 不知具體生物,只透過 Life 的介面(如 CurrentWorld、PosX/PosY、DisplayText、GetNextWorldTask 或生命推進方法)互動,使主體邏輯在未知未來生物前仍可先期完成。

一般化與特殊化的結構調整

將原本 Cell 的共通邏輯上移至 Life(generalization),如座標、所屬世界、顯示行為與生命驅動;保留差異於 Cell(specialization),以利未來衍生新生命型態時只需擴充子類別。World 端一律以 Life 操作任何衍生類別,既減少耦合也保證擴充性,達成「未知的 Life 仍能在既有 World 中存活」的目標。

規則擴充:加入感染機制以驗證設計彈性

在原四條生存規則外新增第五條「感染」規則:正常細胞以(1+感染鄰居數×5)% 機率被感染;感染持續三次狀態後痊癒;感染中有 10% 死亡機率。畫面符號上以●為正常存活、◎為感染、○為死亡,能觀察擴散。藉此測試抽像化是否足以支持需求變更,並確保 World/Life 契約不需改動。

具體實作重點:狀態、顯示與生命推進

Cell 新增 InfectedCount 判斷 IsInfected,DisplayText 依生/感染/死輸出不同符號。WholeLife 迴圈流程:計算鄰居活數與感染數;依規則表決定 IsAlive;若已感染則遞減感染次數並檢查死亡機率;若未感染則依感染鄰居數計算感染機率;每步以隨機延遲推進。結尾釋放資源。此變更僅影響子類別邏輯,未觸及 World 對 Life 的抽像互動。

主程式與動態聯結:鬆耦合的執行期擴充

GameHost 僅負責建立 World 與大量 Cell,並將 Cell 放入 World;World 完全以 Life 操作,不含任何對 Cell 的硬依賴。執行期由動態聯結導向 Cell 的覆寫行為,使主程式在設計完成後仍可納入新規則或新生物而不需改動,印證抽像化帶來的可演化性。

避免過度設計:在抽像化與實用性間取捨

作者提醒新手常因「預知未知需求」而過度設計,如為 1+1 預留列印、四則運算模式等,導致複雜度上升與成本浪費。正確策略是:用抽像化隔離變化,維持簡潔契約;待需求成形時透過子類別特化擴充。後續將以草原生態(草、羊、虎)在不改 World/Life 的前提下替換模擬內容,進一步驗證架構的通用性與穩健度。

資訊整理

知識架構圖

  1. 前置知識:
    • 物件導向基礎:類別、繼承、介面、抽象化、封裝、多型
    • C# 語言基礎:class/virtual/override、IEnumerable、yield return、Random、TimeSpan
    • UML 類別圖基本閱讀
    • 生命遊戲(Conway’s Game of Life)的規則與棋盤鄰居概念
  2. 核心概念:
    • 抽像化(Abstraction):以 Life 定義「生命」的共同行為與狀態,隔離實作細節
    • 一般化/特殊化(Generalization/Specialization):共同特性上移至 Life,特殊行為留在衍生類別(如 Cell)
    • 世界與生命的互動協定:World 僅透過 Life 抽象型別溝通(例如 GetNextWorldTask、DisplayText、座標等)
    • 動態聯結/多型:執行期依衍生類別(Cell)的實作執行行為,主程式無需修改
    • 可演化架構:在不改 World/Life 的前提下擴充新規則或新生物(感染規則、草-羊-虎生態)
  3. 技術依賴:
    • World 依賴 Life 的抽象契約,不依賴 Cell 細節
    • Cell 繼承 Life,實作具體行為(如 WholeLife 邏輯、DisplayText、感染狀態)
    • 執行流程依賴 IEnumerable/Iterator(yield)驅動生命週期節奏
    • 隨機性與機率判斷(Random、概率方法)影響狀態轉移
  4. 應用場景:
    • 生命遊戲、群體行為與生態系模擬(可替換規則與物種)
    • 需高度擴充與變更彈性的系統(插件式架構、規則引擎)
    • 教學示例:展示抽像化、繼承、多型、動態聯結的實戰
    • 面對未明確需求(變更頻繁)時的設計應對

學習路徑建議

  1. 入門者路徑:
    • 了解生命遊戲基本規則與棋盤鄰居計算
    • 複習 OOP 四大特性與 C# 基礎語法
    • 先以單一 Cell 規則實作 World + Cell(無抽像化)
    • 觀察改規則時的痛點,理解為何需要抽像化
  2. 進階者路徑:
    • 將 Cell 共通邏輯上移至 Life,World 僅依賴 Life
    • 引入 IEnumerable/Iterator 模式(yield)管理週期
    • 練習用類別圖描述 World/Life/Cell 的關係
    • 加入新規則(如感染、痊癒、死亡機率)驗證架構穩定性
  3. 實戰路徑:
    • 在不改 World/Life 前提下,新增不同生物類別(如 Herb、Sheep、Tiger)與其交互規則
    • 提煉共用介面或抽象基底(考慮以 interface 與 abstract class 分責)
    • 加入可配置的規則來源(例如 JSON 策略),驗證擴充性
    • 撰寫單元測試覆蓋鄰居計算、機率事件、狀態轉移

關鍵要點清單

  • 抽像化(Abstraction):用 Life 表達生命的共同概念,隔離具體實作與細節(優先級: 高)
  • 一般化與特殊化:共通上移、差異下放,確保結構清晰可演化(優先級: 高)
  • 依賴抽象而非具體:World 只認得 Life,不認得 Cell,降低耦合(優先級: 高)
  • 動態聯結/多型:執行期選擇正確的衍生行為,支援規則/物種擴充(優先級: 高)
  • 生命週期驅動:以 IEnumerable/yield 實作 WholeLife,統一時序與狀態流(優先級: 中)
  • 規則封裝:將生存、死亡、復活、感染等規則封裝於衍生類別(優先級: 高)
  • 狀態建模:IsAlive、IsInfected、InfectedCount 等明確狀態與轉移(優先級: 中)
  • 鄰居計算:FindNeighbors 與鄰居數量驅動狀態決策(優先級: 高)
  • 機率事件處理:InProbability 與隨機性帶來非決定性(優先級: 中)
  • 顯示分離:DisplayText 僅負責表達狀態,不污染邏輯(優先級: 中)
  • 可擴充性驗證:新增「感染規則」而不改 World/Life,實證設計品質(優先級: 高)
  • 單一職責:World 管理空間與時序,Life/Cell 管理行為與狀態(優先級: 高)
  • 需求變更策略:避免過度設計,先抽像化契約,再以最小成本試擴充(優先級: 高)
  • 測試導向演進:以測試保護鄰居計算與轉移規則,支撐重構(優先級: 中)
  • UML 輔助溝通:以類別圖釐清 World-Like-Cell 關係與依賴(優先級: 低)





Facebook Pages

AI Synthesis Contents

Edit Post (Pull Request)

Post Directory