概述#
本章開始進入薪資系統(Payroll System)的案例研究。這是一個簡單的批次薪資應用程式,透過交易(transaction)驅動。系統需要根據指定的排程支付員工薪水,並依照各種規則計算應付金額和扣款。
初步規格#
系統需要處理三種員工類型:
- 時薪員工(Hourly):依照時卡(time card)記錄的工時計薪,每週五支付。時薪超過 8 小時的部分以 1.5 倍計算。
- 月薪員工(Salaried):固定月薪,每月最後一個工作日支付
- 底薪加佣金員工(Commissioned):固定底薪加上銷售收據(sales receipt)的佣金比例,每隔一週的週五支付
支付方式有三種選擇:
- 郵寄支票(Mail)到指定地址
- 銀行轉帳(Direct Deposit)到指定帳戶
- 留存於出納(Hold),由出納保管
部分員工屬於工會(Union)。工會會費按週從薪水扣除,工會也可能對個別會員徵收服務費(service charge),從下次薪資中扣除。
Use Cases#
Use Case 1:新增員工#
系統接收 AddEmp 交易,包含員工的基本資料(ID、名字、地址)和薪資分類資訊。
Use Case 2:刪除員工#
系統接收 DelEmp 交易,以員工 ID 刪除指定員工。
Use Case 3:登記時卡#
系統接收 TimeCard 交易,記錄時薪員工的工作日期和時數。
Use Case 4:登記銷售收據#
系統接收 SalesReceipt 交易,記錄佣金員工的銷售日期和金額。
Use Case 5:登記工會服務費#
系統接收 ServiceCharge 交易,記錄工會會員的服務費。
Use Case 6:變更員工資料#
系統接收 ChgEmp 交易,可變更員工的姓名、地址、薪資分類、支付方式、工會會籍等。
Use Case 7:發放薪資#
系統接收 Payday 交易,產生當天應付薪資的員工薪資記錄,包含應付金額和扣款。
初步設計反思#
從 Use Case 思考類別結構#
最直覺的做法是根據員工薪資類型建立繼承階層:

Figure 26.2: Possible Employee class hierarchy
但作者在分析 Use Case 6(變更員工資料)時發現這個設計有問題——如果時薪員工需要變更為月薪員工,在繼承體系下意味著要銷毀原有物件並建立新物件,這是很不方便的。
重點: 作者指出,Use Case 6 暗示著不應該用繼承來區分員工薪資類型。當員工的薪資分類可以動態變更時,應該使用 Strategy 模式——將薪資計算、支付方式等抽取為獨立的策略物件,讓
Employee透過委派來使用它們。這樣變更薪資類型只需要替換策略物件,不需要替換員工物件本身。
資料庫是實作細節#
作者提出一個重要的設計原則:資料庫是實作細節,應該盡可能延遲決定。在初步設計中不應該考慮使用什麼資料庫技術,因為抽象的薪資系統概念不需要依賴資料庫的存在。
補充: 在此階段,作者選擇將員工資料暫存在簡單的資料結構中(如
Hashtable),將資料庫的具體實作推遲到系統運作正確之後再處理。
核心模型#
經過反覆分析,最終設計出薪資系統的核心模型:

Figure 26.6: Revised class diagram for Payroll: the core model
Employee 類別持有三個策略物件:
PaymentClassification:負責計算薪資(如何算錢)PaymentSchedule:負責判斷支付時程(何時發錢)PaymentMethod:負責處理支付方式(怎麼發錢)
PaymentClassification 抽象#
PaymentClassification 是一個抽象類別,有三個衍生類別:
HourlyClassification:持有時薪費率和時卡集合,依工時計算薪資

Figure 26.8: Calculating an hourly employee's pay
CommissionedClassification:持有底薪、佣金比例和銷售收據集合

Figure 26.9: Calculating a commissioned employee's pay
SalariedClassification:持有月薪金額

Figure 26.10: Calculating a salaried employee's pay
PaymentSchedule 抽象#
在初步分析 Use Case 時,支付排程似乎不需要獨立抽象——時薪員工總是每週五付、月薪員工總是月底付。但作者深入思考後發現,支付排程與薪資分類並非總是綁定的:如果客戶未來想要月薪員工也改成每週發薪呢?

Figure 26.11: Static model of a Schedule abstraction
抽取 PaymentSchedule 後,三個衍生類別各自負責判斷特定日期是否為發薪日:
WeeklySchedule:每週五MonthlySchedule:每月最後一個工作日BiweeklySchedule:每隔一週的週五
技巧: 這展示了尋找底層抽象(Finding Underlying Abstractions)的重要性。表面上看起來緊密綁定的概念(薪資分類與支付排程),在深入分析後可能是獨立的維度。將它們分離後,系統對未來需求的變更具有更好的擴展性。
PaymentMethod 抽象#
支付方式是 Use Case 中已經明確描述的三種選項。PaymentMethod 介面有三個實作:
HoldMethod:將支票留存於出納DirectMethod:銀行轉帳MailMethod:郵寄支票
Affiliation 抽象#
工會會籍也被抽取為獨立的抽象——Affiliation 介面:

Figure 26.13: Static structure of Affiliation abstraction

Figure 26.14: Dynamic structure of Affiliation abstraction
UnionAffiliation:持有會費費率和服務費集合,負責計算工會相關的扣款NoAffiliation:代表不屬於任何工會,扣款計算回傳零(這是一種 Null Object 模式的應用)
關鍵設計決策#
- Strategy 優於繼承:
Employee不繼承不同的薪資子類別,而是持有可替換的策略物件 - 資料庫是實作細節:延遲資料庫決策,先用記憶體資料結構
- 分離獨立的變化維度:薪資分類、支付排程、支付方式、工會會籍各自獨立變化
- Null Object 的應用:未加入工會的員工使用
NoAffiliation,避免到處檢查 null - 交易驅動設計:所有操作都透過交易物件執行,這是 Command 模式的應用
補充: 本章還沒有寫任何程式碼。作者透過分析 Use Cases 和反覆推敲設計,建立了系統的核心抽象模型。這正是敏捷設計的精神——在開始編碼前,透過適度的分析建立足夠的理解,但不過度設計。下一章將開始實作這個模型。