架構面試題 #4 - 抽象化設計;折扣規則的設計機制 (06/25 補完)
問題與答案 (FAQ)
Q&A 類別 A: 概念理解類
A-Q1: 什麼是抽象化(Abstraction)?
- A簡: 抽象化是隱藏細節、提取重點,讓主流程只依賴介面與核心概念,便於擴充與維護。
- A詳: 抽象化的核心是「隱藏細節、提取重點」。在折扣系統中,重點是「購物車能以一致流程取得折扣結果」,細節如各種折扣計算法則則被封裝於規則內。抽象化讓主流程不受規則變化影響,只透過一致的介面進行互動;當未來增加或調整規則時,對主流程零或極小衝擊,大幅提高可維護性與擴充性。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q2, A-Q6, B-Q1
A-Q2: 為什麼抽象化是介面(Interface)設計的核心?
- A簡: 介面表達抽象化邊界,主流程僅依賴介面,規則細節可替換且互不影響。
- A詳: 介面定義了「主流程所需、規則應回應」的最小必要契約,將可變的實作細節抽離。以 RuleBase.Process(cart) 為例,購物車僅呼叫此介面取得折扣,無需理解規則內部演算法。良好介面讓多型得以發揮,使新增規則能即插即用,維持穩定的主流程,這是可演進架構的重要關鍵。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q2, B-Q11, C-Q1
A-Q3: 抽象化與封裝(Encapsulation)的關係?
- A簡: 抽象化是目標,封裝是手段;封裝細節以呈現抽象化的重點與介面。
- A詳: 抽象化旨在保留核心概念、忽略次要細節;封裝則是將細節藏於類別內部,不對外暴露。以折扣系統為例,RuleBase 封裝每種折扣的計算過程,只暴露 Process 介面;主流程只看抽象結果(Discount 列表),不關心內部細節。封裝成功,抽象化才落地,讓變動集中於內部、不波及外部。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q4, B-Q2
A-Q4: 抽象化與繼承、多型的關係?
- A簡: 繼承將共通性一般化,多型讓不同實作以同一介面被一致呼叫。
- A詳: 繼承是一般化(generalization)的技術手段,萃取規則共通面向到基底類別(RuleBase);多型允許以 RuleBase 型別操作各種規則實作(BuyMoreBoxes、TotalPrice、Pairing…)。這使 CheckoutProcess 只需依賴 RuleBase,即可按順序驅動各規則、收集 Discount,達成對變動的穩健封裝。
- 難度: 初級
- 學習階段: 核心
- 關聯概念: B-Q1, B-Q11
A-Q5: 什麼是 ADT(抽象資料型別),與本文的關聯?
- A簡: ADT以抽象方法規範資料操作;本文以類別抽象折扣與購物車狀態。
- A詳: ADT強調「只暴露必要操作,不暴露內部結構」。本文以 Product、Discount、CartContext、RuleBase、POS 建立抽象資料與操作介面。購物車狀態(CartContext)與折扣規則(RuleBase)都是 ADT 思維的實踐:對外提供明確方法、對內隱藏處理細節。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q6, B-Q4
A-Q6: 折扣抽象化的「重點」是什麼?
- A簡: 重點是購物車可用一致流程取得折扣列表與金額,而非規則內部算法。
- A詳: 主流程關心的是「哪些商品符合哪些規則、折多少」,而非各規則如何算。因此抽象化的焦點在規範:輸入購物車狀態,輸出折扣結果(Discount[])。RuleBase.Process(cart) 是核心契約,讓主流程只聚焦計價邏輯(總額-折扣),忽略規則細節。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q2, B-Q13
A-Q7: 折扣抽象化應隱藏哪些「細節」?
- A簡: 隱藏各規則算法、分群與配對方式、權重順序與排除邏輯等細節。
- A詳: 折扣規則差異極大,如滿件、配對、滿額、第二件折、同品加購、跨區價…等。這些演算法、資料結構(排序、分組、配對)、以及排他條件屬於規則內部細節,應封裝於各規則中,對外只以 Discount 回報結果,避免主流程耦合細節。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q16, B-Q8
A-Q8: 用「歸納法」處理折扣與「抽象化設計」有何差異?
- A簡: 歸納法易被未知規則打破;抽象化則以穩定介面包覆變化面。
- A詳: 歸納法自已知規則歸類模板,但面對未知變種易爆炸(旗標滿天飛、設定複雜、UX變差)。抽象化則先界定穩定介面(RuleBase),把「規則差異」放進可替換的實作;新增規則只擴充類別,不改主流程,能持續應對未知。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q6, C-Q1
A-Q9: 什麼是 RuleBase?核心價值是什麼?
- A簡: RuleBase是折扣規則的抽象基底,定義輸入輸出介面與基本中介資料。
- A詳: RuleBase抽象化各規則的統一介面:Process(CartContext)→IEnumerable
。它攜帶規則名稱、說明、(可選)ExclusiveTag,讓主流程按順序呼叫規則,收集結果、套用排他標記。不需認識具體規則類別,即可完成計價流程。 - 難度: 初級
- 學習階段: 核心
- 關聯概念: B-Q2, C-Q1
A-Q10: Discount 實體扮演什麼角色?
- A簡: Discount描述一筆折扣的來源規則、影響商品集合與折抵金額。
- A詳: Discount是「規則→結果」的最小單位,包含Rule(來源規則)、Products(命中的商品集合)與Amount(折抵金額)。收據可據此列示每項優惠、其對應商品,讓運算透明、稽核容易、除錯便利,亦利於後續報表分析。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q13, C-Q10
A-Q11: Product 的設計重點是什麼?為什麼用 Tags?
- A簡: Product包含SKU/Name/Price/Tags;Tags以貼標籤方式描述規則適用性。
- A詳: 為降低規則與商品耦合,採Tag(#衛生紙、#超值配飲料59…)標記商品屬性與活動適用性。規則據Tags篩選,主流程與資料庫不需了解活動細節;當活動變更,只需調整標籤或規則,不需改商品Schema或主流程。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q5, C-Q3
A-Q12: 為什麼後來把規則輸入從 Product[] 改為 CartContext?
- A簡: CartContext集中購物車狀態,讓規則能取用總價、排除資訊等上下文。
- A詳: 有些規則需依賴總價(如滿額折)、前置規則結果(排他標記)或購物車的可見商品集合。改用CartContext使規則能取得完整上下文,且POS能統一重置狀態、更新排他標記、計算總價,使整體流程更一致也易於擴充。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q4, B-Q16
A-Q13: POS 類別是什麼?核心價值是?
- A簡: POS負責載入已啟用規則、按序執行、整合折扣與計價與收據輸出。
- A詳: POS代表結帳引擎,承載ActivedRules(規則清單)並執行CheckoutProcess:重置狀態、計原價、逐規則Process收集Discount、套用排他標記、扣除折扣、輸出收據。它與規則以介面相依,新增規則無需改POS,確保流程穩定。
- 難度: 初級
- 學習階段: 核心
- 關聯概念: B-Q1, B-Q3
A-Q14: 為什麼規則「順序」很重要?
- A簡: 有互斥或依賴時,先後順序會改變命中的組合與最終金額。
- A詳: 例如先滿件後滿額,可能使滿額失效;互斥時「誰先命中誰獨佔」。因此需可設定Priority/清單順序,並在POS按序執行與標記排他。順序應由業務策略決定,技術上提供穩定的序貫處理與可觀測的結果。
- 難度: 初級
- 學習階段: 核心
- 關聯概念: B-Q17, D-Q3
A-Q15: 什麼是「配對折扣」?有何特性?
- A簡: 依不同品類配對(如鮮食+飲料)給組合價,常含跨區規則與最佳化配對。
- A詳: 配對折扣基於兩類(或多類)商品的標籤配對,給定組合價或折數。特性包含:需從可用商品中配成對、優先以對消費者最有利策略(高價先配)、可能存在跨區配對(49鮮食可與59飲料配成59元),以及落單商品不折扣。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q8, C-Q4
A-Q16: 什麼是「折扣排除」或互斥?
- A簡: 指商品命中某折扣後,需排除另一折扣;可分組或全域互斥。
- A詳: 商業上常要求A與B折扣不可同享。可在框架用ExclusiveTag把命中商品標記,後續規則視該標記跳過命中。亦可分群互斥(同一群不可重覆),或採用「前規則決定」/「後規則檢查」/「系統統一規範」三種策略。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q8, B-Q9
A-Q17: ExclusiveTag 是什麼?如何運作?
- A簡: ExclusiveTag為互斥標籤;命中規則後由POS標記到商品,後續規則據此排除。
- A詳: RuleBase可帶ExclusiveTag值;POS處理完該規則後,將命中的Products加上此標籤。後續規則取得可見商品集合時,呼叫CartContext.GetVisiblePurchasedItems(exclusiveTag)自動排除貼有相同標籤的商品,達成統一的互斥控制。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q8, C-Q5
A-Q18: 為何將「每件一列」而不是用Qty欄位?
- A簡: 每件一列使分組、配對、排序更直覺;避免數量拆分邏輯在規則內爆炸。
- A詳: 折扣多半以件為單位配對或分組。若用Qty就需在規則內再拆分為件,增加處理負擔與錯誤空間。直接用一件一列,可用集合操作(排序、分組、切片)簡潔實作各種規則,維持規則邏輯單純與一致。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q5, B-Q8
A-Q19: 何謂「最佳利益」原則在折扣計算中的應用?
- A簡: 指以對消費者最有利策略選擇配對,如高價優先配對以最大化折抵。
- A詳: 許多規則允許自由配對(任選兩件/多件)。為公平一致,系統應採「對消費者最有利」策略,如在配對折扣中對可配對商品先由高價排序,優先配對高價品,使折抵最大化。此策略需明確化並固化在演算法中。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q7, C-Q4
A-Q20: 良好抽象化的成功指標是什麼?
- A簡: 新增規則無須修改主流程;程式清晰、可測試、可觀測、擴充容易。
- A詳: 指標包含:1) 新增/調整規則僅擴充類別與掛載,POS不變;2) 收據能追溯每筆折扣來源與對應商品;3) 有順序與互斥控制能力;4) 測試可直接以JSON輸入重現結果;5) 規則邏輯簡潔、彼此獨立、可替換。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: C-Q1, D-Q1
Q&A 類別 B: 技術原理類
B-Q1: CheckoutProcess 的整體運作流程為何?
- A簡: 計原價、逐規則處理產生折扣、套互斥標記、扣總折扣、輸出收據。
- A詳: 原理:1) 初始化購物車總額(商品Price總和)、清空已套用折扣;2) 按順序迭代ActivedRules,呼叫rule.Process(cart)取得IEnumerable
;3) 將Discount加入明細,若規則有ExclusiveTag則在命中商品貼標;4) 累加折扣金額,自總額扣除;5) 輸出收據與最終金額。核心組件:POS、CartContext、RuleBase、Discount。 - 難度: 初級
- 學習階段: 基礎
- 關聯概念: A-Q13, B-Q2
B-Q2: RuleBase.Process 背後機制是什麼?
- A簡: 統一介面接收CartContext,輸出多筆Discount,隱藏規則算法。
- A詳: 規則以RuleBase抽象介面對外,Process(CartContext)蒐集可見商品(考慮互斥),基於規則內演算法產出0..N筆Discount。以yield return可邊命中邊回報。統一介面保障POS只依賴抽象型別,多型將呼叫導向具體規則類別。核心組件:RuleBase、CartContext、Discount。
- 難度: 初級
- 學習階段: 核心
- 關聯概念: A-Q9, C-Q1
B-Q3: POS 與 Rule 的互動如何設計?
- A簡: POS按序遍歷規則,呼叫Process並整合結果、處理排他與總額更新。
- A詳: 互動採管線(pipeline)模式:POS持有ActivedRules(按優先序),每一則Rule處理後回報Discount;POS將折扣加入cart.AppliedDiscounts,並在有ExclusiveTag時貼標於命中商品,再更新總額。此設計確保POS穩定、規則可插拔、順序可控。
- 難度: 初級
- 學習階段: 核心
- 關聯概念: A-Q14, B-Q17
B-Q4: CartContext 管理哪些狀態?責任為何?
- A簡: 管理購買商品、已套用折扣、總額、可見商品查詢等運算上下文。
- A詳: CartContext封裝結帳過程的共享狀態:PurchasedItems、AppliedDiscounts、TotalPrice,提供GetVisiblePurchasedItems(exclusiveTag)等方法供規則取得可用集合。它將規則間必要資訊傳遞標準化,避免規則彼此耦合,維持主流程簡潔與一致。
- 難度: 初級
- 學習階段: 核心
- 關聯概念: A-Q12, C-Q7
B-Q5: Tags 驅動的商品篩選原理是什麼?
- A簡: 以商品貼標描述活動條件,規則按標籤篩選命中商品集合。
- A詳: 規則不直接綁商品ID或分類,改用語義標籤(如#衛生紙、#超值配鮮食59)。此做法將活動條件由資料側維護,規則僅解讀標籤並執行分組/配對/排序等演算法。此資料驅動方式降低耦合、提升配置與上架彈性。
- 難度: 初級
- 學習階段: 核心
- 關聯概念: A-Q11, C-Q3
B-Q6: 「任N件M折」規則如何運作?
- A簡: 依標籤篩選、排序或分組湊滿N件,依策略計算折扣金額。
- A詳: 流程:1) 取符合Tag的商品;2) 依策略排序(如高價優先);3) 滿N件即命中一次折扣,Amount=選中商品價格和×折扣百分比;4) 清掉已配對商品,繼續掃描。核心組件:RuleBase、CartContext、Discount,實作以yield回報多筆命中。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: C-Q1, C-Q4
B-Q7: 「最佳利益」排序策略如何設計與應用?
- A簡: 先對候選商品依價格遞減排序,優先配高價組合以最大化折抵。
- A詳: 在配對或滿件折扣中,若可自由配對,將候選集合OrderByDescending(Price),從最貴開始配對可產生最大折抵。策略需在規則中固定,保持一致性與可預測性。亦可抽出策略介面以支援不同政策配置。
- 難度: 中級
- 學習階段: 進階
- 關聯概念: A-Q19, C-Q4
B-Q8: ExclusiveTag 互斥機制如何工作?
- A簡: 規則命中後由POS對商品貼ExclusiveTag;後續規則據此排除。
- A詳: 核心步驟:1) RuleBase具ExclusiveTag值;2) POS執行規則後,對每筆Discount.Products貼此Tag;3) 規則取可見商品時呼叫CartContext.GetVisiblePurchasedItems(exclusiveTag)排除;4) 如要分群互斥,可用多組Tag。此為框架層標準化互斥。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q16, C-Q5
B-Q9: 三種互斥策略(前規則、後規則、系統)如何取捨?
- A簡: 前規則決定快但耦合高;後規則檢查彈性高;系統層統一最穩健。
- A詳: 前規則(A決定跳過B)需規則間協作,耦合高;後規則(B檢查A)需取前置結果,實作較複雜;系統層(ExclusiveTag)最標準化、可重用、可分群。規模大、團隊多人時優先用系統層策略,降低整體複雜度。
- 難度: 中級
- 學習階段: 進階
- 關聯概念: A-Q16, A-Q17
B-Q10: 規則「無狀態」與「有狀態」比較?
- A簡: 無狀態回傳Discount較安全;有狀態改CartContext需謹慎重置。
- A詳: 無狀態:規則不改外部狀態,僅回傳結果,利於並行試算與回溯。 有狀態:規則直接改CartContext或商品標記(如ExclusiveTag),流程簡潔但需嚴格Reset與順序控制。實務可混用:由POS負責標記互斥,其餘規則盡量無狀態。
- 難度: 中級
- 學習階段: 進階
- 關聯概念: B-Q1, D-Q5
B-Q11: 用 abstract class 與 interface 定義規則的取捨?
- A簡: abstract class可含共用實作;interface更輕量。本文以抽象類別為主。
- A詳: 抽象類別提供欄位(Name/Note/ExclusiveTag)與共用邏輯(可能的base helper);interface較薄、需搭配外部共用元件。本文規則共享多個中介欄位,選擇abstract class較實用。過度在interface加預設實作會混淆責任邊界。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q2, A-Q9
B-Q12: 「資料驅動」與「程式碼驅動」的跨區配對差異?
- A簡: 資料驅動用表格列出合法配對;程式碼驅動靠多標籤讓規則自動命中。
- A詳: 資料驅動:在規則內(或外部設定)以陣列/表列出(飲料59,鮮食49→59元)組合;修改規則只改資料。 程式碼驅動:商品標多組標籤(鮮食同時#49/#59)讓原規則自然命中。前者集中管理、後者配置彈性高,依場景選用。
- 難度: 中級
- 學習階段: 進階
- 關聯概念: C-Q4, D-Q2
B-Q13: 收據如何綁定折扣與商品?
- A簡: Discount包含Rule與Products;列印時逐項輸出折抵與命中商品。
- A詳: 每筆Discount攜帶Rule(Name/Note)與Products(命中商品清單)與Amount。POS將AppliedDiscounts逐筆列印,可追溯優惠來源、驗證命中條件,方便客服與稽核。同時也利於後續報表與AB Test分析。
- 難度: 初級
- 學習階段: 核心
- 關聯概念: A-Q10, C-Q10
B-Q14: 排序穩定性如何影響折扣結果?
- A簡: 同價位次序會影響配對對象;建議定義穩定排序規則。
- A詳: 多數規則需排序(如高價優先)。若同價,未定義次序可能導致不同配對結果。可根據SKU、加入時間等作穩定次序,以確保結果可重現、可測試、可預期。測試時亦應固定輸入順序。
- 難度: 中級
- 學習階段: 進階
- 關聯概念: D-Q3, C-Q9
B-Q15: 為何使用 yield return 回報多筆折扣?
- A簡: 方便邊掃描邊產生結果,記憶體友善並保持規則實作簡潔。
- A詳: 折扣常是「命中一組→回報一筆」。yield return讓規則以流水方式輸出,不需先建完整清單;POS再逐步累計。若規則需後見之明(全局最佳化),則可先ToList物化再處理,避免延遲執行與狀態變更交互影響。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: D-Q9, C-Q4
B-Q16: 金額計算為何使用 decimal?
- A簡: decimal避免浮點誤差,適合金額運算與稅務/發票精度需求。
- A詳: 金融金額須精確不可有二進位浮點誤差。decimal提供十進位高精度。應統一金額欄位與運算用decimal,並在最後統一四捨五入規則;避免double/float,確保各折扣金額與總額一致、可稽核。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: D-Q8, C-Q10
B-Q17: 規則優先權(Priority)與執行順序的技術實現?
- A簡: 為Rule加Priority欄位,載入時排序;POS按序執行。
- A詳: 可在RuleBase加入Priority,規則由資料庫載入時依Priority與生效期間排序。POS以排序後清單執行,保證互斥/依賴策略的可控性與可配置性。應提供可觀測日志/收據檢視以驗證順序正確。
- 難度: 初級
- 學習階段: 核心
- 關聯概念: A-Q14, C-Q6
B-Q18: 框架層如何實作互斥而不侵入規則實作?
- A簡: 由POS貼ExclusiveTag、CartContext提供可見集合,規則只專注算法。
- A詳: 框架統一互斥:規則保留ExclusiveTag屬性;POS在命中後貼標;CartContext在GetVisiblePurchasedItems時過濾。規則實作只需關心如何命中與計算折扣。此責任分離能避免規則間互相查詢、降低耦合。
- 難度: 中級
- 學習階段: 進階
- 關聯概念: A-Q17, C-Q5
B-Q19: 如何避免多次結帳的狀態汙染?
- A簡: 在POS開始前重置CartContext與規則內部暫存結構。
- A詳: 反覆結帳時需清空AppliedDiscounts、重算TotalPrice、清理規則內部queue/cache等。可在POS.CheckoutProcess開頭統一reset;若規則持有內部集合,應在Process內建立區域集合或每次先清空,避免前次殘留影響。
- 難度: 中級
- 學習階段: 進階
- 關聯概念: D-Q5, C-Q7
B-Q20: 如何以測試資料(JSON)驅動測試與重現?
- A簡: 以去正規化JSON列出每件商品,載入後直接結帳比對輸出。
- A詳: 測試用JSON每件商品一列(含SKU/Name/Price/Tags),載入後放入CartContext.PurchasedItems,執行POS.CheckoutProcess,再比對收據與總額。此做法易重現真實情境、便於PR回歸與案例分享。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: C-Q9, D-Q2
Q&A 類別 C: 實作應用類(10題)
C-Q1: 如何實作第一個規則:任N件結帳M折?
- A簡: 實作BuyMoreBoxesDiscountRule:依Tag篩選、滿N件即yield折扣。
- A詳: 步驟:1) 繼承RuleBase,建立BuyMoreBoxesDiscountRule(int n, int percentOff);2) Process:取得符合Tag的商品,按價格排序(高價優先),每滿n件計算Amount=小計×percentOff/100,yield Discount(含Products、Rule);3) 將此規則加入ActivedRules末端。程式碼片段:Amount = matched.Sum(p=>p.Price)*percentOff/100。注意:排序策略、物化集合避免延遲陷阱。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q6, B-Q7
C-Q2: 如何新增滿額折抵規則(滿1000折100)?
- A簡: 建立TotalPriceDiscountRule:檢查cart.TotalPrice,命中即回報一筆折扣。
- A詳: 步驟:1) 繼承RuleBase建立TotalPriceDiscountRule(decimal min, decimal off);2) Process:if(cart.TotalPrice>=min) yield new Discount{Amount=off, Products=cart.PurchasedItems.ToArray(), Rule=this};3) 放在合適位置(通常置後)。注意:此規則需CartContext提供總價,故要在POS先計原價、前序折扣已生效時再檢查。
- 難度: 初級
- 學習階段: 核心
- 關聯概念: A-Q12, B-Q1
C-Q3: 如何為商品加上Tags並在UI設定?
- A簡: 資料層以HashSet
儲存;後台以多選控件配置與批量套用。 - A詳: 實作:Product.Tags為HashSet
。後台商品編輯頁提供標籤多選/搜尋;可提供批量操作(依品類/價位/SKU規則),也可自動化標籤(上架活動時批量貼標)。注意:控制標籤命名規則一致性(如「超值配飲料59」),避免拼寫差異造成規則失效。 - 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q5, D-Q2
C-Q4: 如何實作「配對折扣」與跨區規則?
- A簡: 以折扣表列舉(飲料Tag,鮮食Tag,組合價),高價優先配對產生Discount。
- A詳: 步驟:1) 在規則中宣告(discount_table):(drinkTag, foodTag, price)[];2) 對每組表項:取得可見商品中符合各Tag集合,依價高到低配對,同索引成對;3) 每對回報Discount,Amount=價和-組合價;4) 跨區:在表中加入跨區對應或商品同時貼多個Tag。程式碼:products.OrderByDescending(p=>p.Price);yield每筆。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q8, B-Q12
C-Q5: 如何設定ExclusiveTag以禁止重複優惠?
- A簡: 在互斥規則上設定相同ExclusiveTag;POS命中後貼標,後續規則自動略過。
- A詳: 步驟:1) 在RuleBase新增ExclusiveTag欄位;2) LoadRules時對互斥規則指定同一值(如”ex”);3) POS在處理完每筆Discount後,對Products加上此Tag;4) 規則取可見商品時呼叫cart.GetVisiblePurchasedItems(exclusiveTag);5) 測試互斥生效。最佳實踐:以群組命名(ex: groupA/groupB)支援多群互斥。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q8, D-Q1
C-Q6: 如何配置規則優先權(Priority)?
- A簡: 在RuleBase加Priority,載入時排序;互斥與依賴以順序控制。
- A詳: 步驟:1) RuleBase新增Priority屬性;2) LoadRules自DB載入後OrderBy(Priority);3) POS使用排序後規則執行;4) 測試「先滿件後滿額」「A互斥於B」等情境。注意:Priority要與行銷政策同步管理,並暴露在報表/收據以利溝通。
- 難度: 初級
- 學習階段: 核心
- 關聯概念: B-Q17, D-Q3
C-Q7: 如何從傳遞Product[]重構為CartContext/POS?
- A簡: 封裝購物車狀態至CartContext,將流程集中到POS以一致驅動規則。
- A詳: 步驟:1) 新增CartContext含PurchasedItems/AppliedDiscounts/TotalPrice與GetVisiblePurchasedItems();2) 新增POS承載ActivedRules與CheckoutProcess();3) 調整RuleBase.Process簽章接受CartContext;4) 將主程式只負責載入產品/規則、呼叫POS;5) 驗證收據輸出一致。注意:重構需保留測試案例不變以比對前後結果。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q4, B-Q19
C-Q8: 如何用DI(Dependency Injection)註冊與載入規則?
- A簡: 以容器註冊所有RuleBase實作,按需求解析並依Priority排序執行。
- A詳: 步驟:1) 在.NET以IServiceCollection註冊具體規則(AddSingleton<RuleBase, XxxRule>());2) 於組合根解析IEnumerable
;3) 根據配置(DB/檔案)過濾生效規則並排序;4) 注入POS.ActivedRules。程式碼:services.AddSingleton<RuleBase, DiscountRule1>(); 注意:規則參數化可來自設定/DB。 - 難度: 中級
- 學習階段: 進階
- 關聯概念: A-Q2, B-Q11
C-Q9: 如何建立測試JSON並讀取執行?
- A簡: 每件一列含SKU/Name/Price/Tags;反序列化後放入CartContext。
- A詳: 步驟:1) 建立products.json,每件商品一列並列Tags陣列;2) 以JsonConvert.DeserializeObject<Product[]>(file)載入;3) 逐件補上流水Id與必要欄位;4) 放入cart.PurchasedItems;5) 呼叫POS.CheckoutProcess(cart)輸出;6) 比對折扣與總額。最佳實踐:針對每規則建最小案例與綜合案例。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q20, D-Q2
C-Q10: 如何產生含命中商品的收據明細?
- A簡: 逐行列印商品,再逐筆列AppliedDiscounts含Rule.Name與Products。
- A詳: 步驟:1) 列印「購買商品」:SKU/Price/Name/Tags;2) 列印「折扣」:每筆Discount的Amount、Rule.Name/Note;3) 對每筆Discount.Products逐行列出命中商品;4) 列印總額;5) 保持欄位對齊與可讀性。注意:金額使用decimal與統一格式化,便於客訴與稽核。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q13, B-Q16
Q&A 類別 D: 問題解決類(10題)
D-Q1: 出現折扣重複計算怎麼辦?
- A簡: 啟用ExclusiveTag互斥,POS貼標後由規則取得可見集合自動排除。
- A詳: 症狀:同商品同時享受多個不該疊加的折扣。可能原因:未設互斥、順序錯誤、規則自行忽略失敗。解法:為互斥規則設定相同ExclusiveTag;在POS命中後貼標;規則以GetVisiblePurchasedItems(exclusiveTag)取得集合。預防:建立互斥分群策略與測試。
- 難度: 初級
- 學習階段: 核心
- 關聯概念: B-Q8, C-Q5
D-Q2: 配對折扣未生效的原因與解法?
- A簡: 多因標籤不一致或未定義跨區;修正Tag或在表格增加跨區組合。
- A詳: 症狀:應配對卻按原價。原因:商品未貼正確Tag、Tag拼寫不一致、規則未列跨區組合、排序導致落單。解法:檢查商品Tags、統一命名、在discount_table加跨區對或給鮮食多貼標籤。預防:用測試JSON驗證各組合。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q12, C-Q4
D-Q3: 規則順序不同導致總額差異,如何診斷?
- A簡: 檢查Priority與收據序列,調整順序以符合業務策略。
- A詳: 症狀:換順序金額變動。原因:互斥或依賴關係被打破。步驟:列印收據逐筆比對折扣命中順序、金額、命中商品;調整Priority使先滿件後滿額或先高權重規則;再回歸測試。預防:將順序策略文檔化並固化於設定。
- 難度: 初級
- 學習階段: 核心
- 關聯概念: B-Q17, C-Q6
D-Q4: 新增規則卻需要修改主程式,如何改善?
- A簡: 檢討抽象化邊界,確保主流程只依賴RuleBase介面與CartContext。
- A詳: 症狀:每加規則都要改POS。原因:介面未抽象到位(例如規則需直接存取POS內部或缺少上下文)。解法:統一以RuleBase.Process(CartContext)回傳Discount;POS只管順序、互斥、總額;規則透過Tag/CartContext取得所需資訊。預防:以擴充規則為驗收標準。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q20, C-Q7
D-Q5: 多次結帳產生狀態汙染怎麼辦?
- A簡: 在POS重置CartContext,規則內部不可持久化暫存或需明確清空。
- A詳: 症狀:第二次結帳結果受前次影響。原因:未清空AppliedDiscounts、規則內queue未清理。解法:POS開始時清空折扣與重算總額;規則內將暫存改為區域變數或Process內初始;必要時提供Reset方法。預防:加上多次結帳回歸測試。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: B-Q19, C-Q7
D-Q6: 「買一送一」與「第二件0元」常算錯,如何處理?
- A簡: 兩者語義不同,建議做成兩個規則,避免以旗標模糊差異。
- A詳: 症狀:有時送的商品未入購物車或計價錯誤。原因:將不同業務語義硬塞同規則,用旗標分支。解法:將「送」視為自動加贈與「第二件0元」視為需拿兩件分開實作;必要時用專屬標籤與配對策略。預防:以測試覆蓋兩情境。
- 難度: 中級
- 學習階段: 核心
- 關聯概念: A-Q8, C-Q1
D-Q7: 折上折或互斥混用時常出問題,怎麼診斷?
- A簡: 明確宣告折上折規則與互斥分群,並用收據驗證命中序列。
- A詳: 症狀:有時重覆折、有時被錯誤排除。原因:未清楚定義哪些可疊加、哪些互斥。解法:制定分群互斥與可疊加白名單;ExclusiveTag區分群組;按Priority執行;收據逐項核對。預防:規則說明(Note)與配置表一致。
- 難度: 中級
- 學習階段: 進階
- 關聯概念: B-Q9, C-Q6
D-Q8: 金額四捨五入或精度問題導致對不上發票?
- A簡: 統一使用decimal與一致的金額格式化/四捨五入規則。
- A詳: 症狀:收據與發票金額小數差異。原因:使用浮點或不同步的四捨五入。解法:全程decimal;統一金額格式與小數位;在最後結算點統一Round;測試中檢查累計誤差。預防:建立金額處理公用模組。
- 難度: 初級
- 學習階段: 基礎
- 關聯概念: B-Q16, C-Q10
D-Q9: 使用yield回傳時,遇到延遲執行導致命中集合異常?
- A簡: 在變更商品標記前先ToList物化折扣結果,或避免中途改集合。
- A詳: 症狀:命中商品與最終列印不一致。原因:延遲執行與後續標記/移除交互。解法:規則Process內或POS端先ToList()物化;或在規則內使用局部集合快照;避免在迭代中修改同一集合。預防:代碼審查與延遲執行知識普及。
- 難度: 中級
- 學習階段: 進階
- 關聯概念: B-Q15, B-Q19
D-Q10: 商品很多時效能不佳,如何優化?
- A簡: 事先分組索引、減少重複掃描、僅對候選集合排序與配對。
- A詳: 症狀:大量商品時規則耗時。原因:每則規則反覆全掃與排序。解法:1) 先依Tag建立候選集合;2) 對候選集合排序而非全集合;3) 常用映射(SKU→商品清單)Cache於Process區域;4) 減少LINQ鏈式重掃;5) 僅在必要時物化。預防:以大資料集壓測調整。
- 難度: 中級
- 學習階段: 進階
- 關聯概念: B-Q6, B-Q19
學習路徑索引
- 初學者:建議先學習哪 15 題
- A-Q1: 什麼是抽象化(Abstraction)?
- A-Q2: 為什麼抽象化是介面(Interface)設計的核心?
- A-Q3: 抽象化與封裝(Encapsulation)的關係?
- A-Q4: 抽象化與繼承、多型的關係?
- A-Q5: 什麼是 ADT(抽象資料型別),與本文的關聯?
- A-Q6: 折扣抽象化的「重點」是什麼?
- A-Q7: 折扣抽象化應隱藏哪些「細節」?
- A-Q11: Product 的設計重點是什麼?為什麼用 Tags?
- A-Q13: POS 類別是什麼?核心價值是?
- A-Q14: 為什麼規則「順序」很重要?
- B-Q1: CheckoutProcess 的整體運作流程為何?
- B-Q2: RuleBase.Process 背後機制是什麼?
- B-Q4: CartContext 管理哪些狀態?責任為何?
- C-Q1: 如何實作第一個規則:任N件結帳M折?
- C-Q9: 如何建立測試JSON並讀取執行?
- 中級者:建議學習哪 20 題
- A-Q8: 用「歸納法」處理折扣與「抽象化設計」有何差異?
- A-Q12: 為什麼後來把規則輸入從 Product[] 改為 CartContext?
- A-Q15: 什麼是「配對折扣」?有何特性?
- A-Q16: 什麼是「折扣排除」或互斥?
- A-Q17: ExclusiveTag 是什麼?如何運作?
- A-Q18: 為何將「每件一列」而不是用Qty欄位?
- A-Q19: 何謂「最佳利益」原則在折扣計算中的應用?
- A-Q20: 良好抽象化的成功指標是什麼?
- B-Q5: Tags 驅動的商品篩選原理是什麼?
- B-Q6: 「任N件M折」規則如何運作?
- B-Q7: 「最佳利益」排序策略如何設計與應用?
- B-Q8: ExclusiveTag 互斥機制如何工作?
- B-Q12: 「資料驅動」與「程式碼驅動」的跨區配對差異?
- B-Q13: 收據如何綁定折扣與商品?
- B-Q15: 為何使用 yield return 回報多筆折扣?
- B-Q17: 規則優先權(Priority)與執行順序的技術實現?
- C-Q2: 如何新增滿額折抵規則(滿1000折100)?
- C-Q4: 如何實作「配對折扣」與跨區規則?
- C-Q5: 如何設定ExclusiveTag以禁止重複優惠?
- D-Q2: 配對折扣未生效的原因與解法?
- 高級者:建議關注哪 15 題
- B-Q9: 三種互斥策略(前規則、後規則、系統)如何取捨?
- B-Q10: 規則「無狀態」與「有狀態」比較?
- B-Q11: 用 abstract class 與 interface 定義規則的取捨?
- B-Q12: 「資料驅動」與「程式碼驅動」的跨區配對差異?
- B-Q14: 排序穩定性如何影響折扣結果?
- B-Q18: 框架層如何實作互斥而不侵入規則實作?
- B-Q19: 如何避免多次結帳的狀態汙染?
- C-Q6: 如何配置規則優先權(Priority)?
- C-Q7: 如何從傳遞Product[]重構為CartContext/POS?
- C-Q8: 如何用DI(Dependency Injection)註冊與載入規則?
- D-Q1: 出現折扣重複計算怎麼辦?
- D-Q3: 規則順序不同導致總額差異,如何診斷?
- D-Q5: 多次結帳產生狀態汙染怎麼辦?
- D-Q7: 折上折或互斥混用時常出問題,怎麼診斷?
- D-Q10: 商品很多時效能不佳,如何優化?