Polymorphism

尤米爾·弗里茲是最初的巨人之力擁有者。
在她死後,國王為了維持這股力量,命令她的三個女兒 ── 瑪莉亞、羅塞、希娜 ── 吃下分屍後的母體。於是,巨人之力被分散成 九大巨人:始祖、進擊、超大型、鎧甲、女巨人、獸、戰鎚、顎、車力

諫山創實作了多型(Polymorphism)
- 同樣都是「巨人」,卻能展現出完全不同的外觀、能力與戰鬥風格。
- 基底類別
Titan定義了巨人的共通特性,而每個具體巨人(ColossalTitan、ArmoredTitan…)則是子類別,實作或覆寫不同的行為 - 後期的 Eren 同時擁有「進擊的巨人」、「始祖巨人」、「戰鎚巨人」的能力,就像一個物件同時實作多個介面,或覆寫了不同的行為
- 共通限制:因為尤米爾只活了 13 年,她的繼承者也都受「13 年壽命」的約束,這就像基底類別定義的共同規則,所有子類別都必須遵守

首先是靜態多型(Compile-time Polymorphism)中的「方法多載(Method Overloading)」,方法多載的本質,是讓「同一個行為概念」在不同輸入條件下,有最貼近語意的實作,而不是逼使用者記一堆不同方法名稱
在巨人與人類型態,不同角色都會「攻擊」,但攻擊方式可能帶有不同條件(武器、對象、力量),我們先定義一個共同的行為名稱:Attack 代表「攻擊」這個抽象行為,不管是人、巨人、還是拿武器
為了實現不同的攻擊行為, overload 用參數型別與數量來區分不同情境
- 沒參數 → 最基本、預設的攻擊方式
- string target → 有明確攻擊對象
- int titanStrength → 以力量為核心的攻擊
- string weapon + int strength → 特殊狀態+額外條件
編譯器在編譯期就完成「要叫哪一個 Attack」的判斷,因此呼叫端一寫出參數,對應的方法版本就已經被決定,執行時不需要再判斷 if / switch,每個方法各自專注處理自己的邏輯,不用在一個方法裡塞一堆條件分支,實現了每個版本語意清楚、責任單一的目的
1 | public class Warrior |
同樣是 Attack(),但編譯器會根據呼叫時的參數,決定實際使用哪個版本
- 普通士兵揮刀(Attack())
- 士兵 + 立體機動裝置 → 特化對巨人的攻擊(Attack(string target))
- 巨人肉搏(Attack(int titanStrength))
- 戰鎚巨人召喚武器(Attack(string weapon, int titanStrength))
👉 靜態多型的關鍵在於在程式「執行前」就能決定要呼叫哪個方法
具體的商業實務情境 - 金流系統的「請款(Charge)」流程
假設你在做一個金流/帳務系統,有一個核心行為叫「向客戶請款(Charge)」
不管前面接的是訂單系統、訂閱系統、還是人工後台,最後都會走到這個動作
但實務上會出現這幾種「資料完整度」
- 已知客戶與金額 → 立即請款
- 已知客戶、金額、付款方式 → 指定管道請款
- 只知道付款 Token(例如信用卡 Token) → 快速請款
- 已有授權(Auth)結果 → Capture 請款
這不是角色問題,也不是會員問題,而是「現在你手上有多少資訊」的問題
Step 1:先定義真正的商業行為名稱
1 | Charge(...) |
不是:
ChargeByCustomer
ChargeByToken
CaptureAuthorizedPayment
因為在帳務語言裡,它們本質上都是「收錢」
Step 2:用參數「明確表達」目前已知的商業狀態
1 | public class PaymentService |
這裡的重點是方法簽名就是「現在系統知道什麼」的誠實呈現
Step 3:編譯器在這裡扮演「流程守門員」
你不可能寫出這種曖昧不清的呼叫 Charge(null, 1000);
Step 4:每一條 Charge 路徑都能對應到乾淨的實作
1 | public void Charge(string authorizationId) |
多載可以自然區分「外部輸入」與「內部狀態流轉」,而不需要額外的 flag
用同一個「父類別介面」在呼叫方法,但真正的行為要留到執行時,交給「實際物件是誰」來決定,這樣系統才能在不改呼叫端的情況下自由擴充,可以讓「使用方式固定、行為可以替換」

Method Overriding = 不同巨人繼承相同基底,但行為不同
1 | public abstract class Titan |

創哥定義一個父類別 Titan,它代表一個「抽象概念」,只要是巨人,就一定能攻擊,Attack() 被定義成 virtual,意思是我先給你一個預設版本,但我知道你之後一定會想改,子類別繼承同一個基底類別ColossalTitan : Titan、ArmoredTitan : Titan,這一步是在建立「是某一種 Titan」的關係,而不是獨立的類別,子類別用 override 重寫行為,方法名稱一樣、參數一樣,但內容完全不同,這是在明確告訴編譯器我不是新增一個方法,我是在替換父類別的那個行為
假設
1 | void DoAttack(Titan titan) |
完全不需要 if (titan is ColossalTitan)、也不需要 switch 判斷型別,新增一個 BeastTitan 時,這個方法一行都不用改,這就是多型的實際價值,行為的差異,被包在物件裡,而不是灑在流程控制裡
這個設計其實是在保護呼叫端,不被改動影響,多型是為了減少條件判斷、降低修改風險、提高系統擴充性,如果未來有 20 種巨人,真正穩定的不是巨人的數量,而是你「呼叫 Attack 的方式永遠不變」這件事
商業情境 - 促購規則的「共通判斷骨架」與可覆寫的差異點(Promotion Rule Base)
促購規則不是只在比「折多少」,而是要先通過一組「公司不能漏的基本門檻」,子類別只能在這個門檻之上加條件,促購規則不是只在比「折多少」,而是要先通過一組「公司不能漏的基本門檻」,子類別只能在這個門檻之上加條件。
1 | /// <summary> |
把「促購一定要有的共同狀態」集中在 Base Class像這些屬性
Enabled
Since / Until / MultipleDates
Priority / DynamicPriority
ExclusiveRuleIds / ExclusiveTags
TagsWhenMatched / TagsWhenMismatched
SourceType
Version
TypeFullName
這些是「所有促購都必須被同一套引擎理解」的資料結構,為了建立制度語言,假設有三種促購滿額折、指定商品折、會員等級折,它們全部都要不能在活動外套用、不能在停用狀態套用、能被促購引擎排序、排他、標記,這就是 PromotionRuleBase 在做的事
介面多型的核心是先約定「你一定做得到什麼」。這讓使用者只在意「能力是否存在」,而不是「具體是哪一個實作類別」
每個巨人實作了 ITitanShifter,所以可以用同一種型別(ITitanShifter)去操作不同的巨人,保證他們一定具備「變身」與「繼承」的能力

1 | public interface ITitanShifter |

另外,不管是 弗里茲王族 還是 雷伊斯王族,只要是王室血脈,就必須遵守「維護始祖之力的秘密」這個契約
1 | public interface IRoyalFamily |
如果用抽象類別來做,類別就被「身分」綁死,之後想再加其他能力會卡死, 介面讓你只關心能力,不會過早做身分決定
抽象類別的核心價值,是把「一定要一樣的規則」先鎖死,再逼子類別只專心處理「一定會不一樣的行為」,避免專案後期每個人亂玩
Abstract Class = 一部分是統一規則,一部分是各自實作
1 | public abstract class TitanShifter |
抽象類別是「半成品模板」。有些行為是統一的(所有巨人都有後頸弱點)。有些行為必須交由子類別去具體實作(各自的專屬技能)。這就像九大巨人「同源」於尤米爾,因此有共通的限制(壽命 13 年、後頸弱點),但各自的力量卻不同

巨人之力實現了各式各樣的多型
- Static Polymorphism:同名方法,不同參數 → 如攻擊方式依條件不同而改變
- Dynamic Polymorphism:同樣呼叫 Attack(),不同巨人展現不同招式
- Interface Polymorphism:不同類別遵守相同契約 → 巨人繼承者、王室血脈
- Abstract Class Polymorphism:共通規則 + 專屬能力 → 九大巨人同源於尤米爾

多型的核心精神,就是「在共通之中展現差異」。正如巨人世界中,不論如何分支,最後都能追溯到同一個源頭──尤米爾



