規則引擎模式 - upperdine
作為專業或有抱負的軟體工程師,我們通常的任務是將業務規則轉化為計算機可以理解的東西。我們使用類對問題域進行建模,並編寫業務邏輯以反映存在於程式碼庫之外的現實世界規則。當這些業務規則在現實世界中發生變化時,它們也必須在代表它們的程式碼中發生變化,這就是我們領域真正複雜的地方。
定義好的程式碼
軟體工程師的普遍看法是,好的程式碼是可以輕鬆更改的程式碼。另一個是人們應該努力編寫程式碼,就好像下一個編寫程式碼的人將是一個凶殘的精神病患者。兩者都是基於相同原則的不同觀點。
如果您只編寫永遠不會更改的程式碼,那麼您不妨停止閱讀此處,因為這篇文章不適合您。對於我們這些在規則不斷髮展的業務中工作的人,我希望您考慮以下程式碼:
public class ShippingCalculator { public decimal Calculate(BasketDetails details) { if (details.Customer.IsOverseas) { if (details.BasketTotal >= 200) { return 0m; } return 49.99m; } else { if (details.BasketTotal >= 100) { return 0m; } return 19.99m; } } } |
這是一段相當簡單的程式碼,用於計算電子商務網站的運費。規則是:
- 國內運費為 19.99 英鎊
- 國際運費為 49.99 英鎊
- 如果購物籃總額達到國內訂單 100.00 英鎊或國際訂單 200.00 英鎊的門檻,則免運費
假設該公司引入了一項新規則,在 12 月份提供半價國內運費:考慮如何更改此程式碼以適應這種情況。如果我們使用簡單的解決方案,我們可以在 if 語句的國內運輸分支中新增如下內容:
if (DateTime.Now.Month == 12) { return 9.99m; } |
現在想象一下,他們也將此優惠擴充套件到國際訂單。此時程式碼變得複雜,迫切需要重構。當然,您可以使用 C# 的一些更好的功能在一定程度上對其進行清理,但僅此而已。
開放-封閉原則
在過去十年左右的時間裡,如果你有過一次工作面試,你可能不得不解釋一個或多個SOLID原則。其中一個不太被理解,但卻非常重要的原則是開放-封閉原則,它指出,遵守的程式碼應該對擴充套件開放,但對修改封閉。就像大多數SOLID原則一樣,這條原則是故意模糊的,以便給會議的演講者提供一些模糊的東西,但是這條原則的核心是,你應該能夠作為現有程式碼的擴充套件來增加功能,而不是取代它。
聽完這個解釋,你認為上面的程式碼是否堅持了這個原則?為了給運費計算演算法增加一個新的規則,我們不得不在已經很亂的程式碼中增加更多的程式碼,所以答案是否定的。對這段程式碼的每一次修改都有可能引入一個bug,說實話:看起來像這樣的程式碼幾乎從來沒有被單元測試覆蓋過,或者至少沒有最新的測試,所以我們不能自信地對它進行修改。
應用規則引擎模式
我不打算對規則引擎模式進行理論上的概述,而是試圖通過演示我們將要重構的現有實現的程式碼來解釋它。
我們首先需要一個介面來定義規則在我們系統的上下文中是什麼樣子的。
public record ShippingCalculatorRuleResult(bool Applied, decimal Shipping); public record ShippingCalculatorRuleFailedResult() : ShippingCalculatorRuleResult(false, 0m); public record ShippingCalculatorRuleSuccessResult(decimal Shipping) : ShippingCalculatorRuleResult(true, Shipping); public interface IShippingCalculatorRule { ShippingCalculatorRuleResult Calculate(BasketDetails basket); } |
然後,我們需要一個代表我們的規則引擎的類,它通過建構函式接收一個規則集合,並有一個方法來傳遞所有規則執行後的計算結果。
public class ShippingCalculatorRulesEngine { private readonly IReadOnlyCollection<IShippingCalculatorRule> _rules; public ShippingCalculatorRulesEngine(IReadOnlyCollection<IShippingCalculatorRule> rules) { _rules = rules; } public decimal CalculateShipping(BasketDetails basket) { /* We want to return the lowest shipping price that the customer is entitled to.*/ return _rules .Select(r => r.Calculate(basket)) .Where(r => r.Applied) .Min(r => r.Shipping); } } |
現在我們來建立一些規則:
public class InternationalShippingRule : IShippingCalculatorRule { public ShippingCalculatorRuleResult Calculate(BasketDetails basket) { return basket.Customer switch { { IsOverseas: true } => new ShippingCalculatorRuleSuccessResult(49.99m), _ => new ShippingCalculatorRuleFailedResult() }; } } public class BasketTotalRule : IShippingCalculatorRule { public ShippingCalculatorRuleResult Calculate(BasketDetails basket) { return basket switch { { Customer.IsOverseas: true, BasketTotal: >= 200.00m } => new ShippingCalculatorRuleSuccessResult(0m), { BasketTotal: >= 100.00m } => new ShippingCalculatorRuleSuccessResult(0m), _ => new ShippingCalculatorRuleFailedResult() }; } } public class HalfPriceDecemberShippingRule : IShippingCalculatorRule { public ShippingCalculatorRuleResult Calculate(BasketDetails basket) { return DateTime.Now.Month switch { 12 when basket.Customer.IsOverseas => new ShippingCalculatorRuleSuccessResult(24.99m), 12 => new ShippingCalculatorRuleSuccessResult(9.99m), _ => new ShippingCalculatorRuleFailedResult() }; } } |
最後,我們可以在航運計算器類中使用我們的規則引擎結果。
public class ShippingCalculator { private readonly ShippingCalculatorRulesEngine _shippingCalculatorRulesEngine; public ShippingCalculator() { /* We can use reflection to find all rules in the current project. I may cover other ways of doing this in a future post.*/ var ruleType = typeof(IShippingCalculatorRule); IReadOnlyCollection<IShippingCalculatorRule> rules = GetType().Assembly.GetTypes() .Where(p => ruleType.IsAssignableFrom(p) && !p.IsInterface) .Select(r => Activator.CreateInstance(r) as IShippingCalculatorRule) .ToList() .AsReadOnly()!; _shippingCalculatorRulesEngine = new ShippingCalculatorRulesEngine(rules); } public decimal Calculate(BasketDetails basket) { return _shippingCalculatorRulesEngine.CalculateShipping(basket); } } |
因此,基於這段程式碼,我們在規則引擎的實現中擁有以下元件:
- 一個所有規則都必須實現的契約
- 規則的實現
- 一個執行所有規則並返回編譯後的值或結果的引擎
所有其他的東西,比如結果型別,只是一個實現細節。實際上,你所需要的只是上面列出的三個元件(以及一種將規則引入規則引擎的方法),你就可以開始了。
新增一個新的規則
現在我們有了一個工作的規則引擎實現,我們能夠比以前更快地新增新規則。比方說,企業決定將客戶生日時的免費送貨門檻減半,我們只需新增一個新的規則實現,其餘的就都搞定了。
public class BirthdayCouponShippingRule : IShippingCalculatorRule { public ShippingCalculatorRuleResult Calculate(BasketDetails basket) { if (basket.Customer.DateOfBirth != DateTime.Now.Date) return new ShippingCalculatorRuleFailedResult(); return basket switch { { Customer.IsOverseas: true, BasketTotal: >= 100.00m } => new ShippingCalculatorRuleSuccessResult(0m), { BasketTotal: >= 50.00m } => new ShippingCalculatorRuleSuccessResult(0m), _ => new ShippingCalculatorRuleFailedResult() }; } } |
新增一個規則就是這麼簡單,而且由於這些規則是純函式,這使得它們在測試時絕對是一個夢想 如果你沒有掌握函數語言程式設計的理論,純函式是一個沒有副作用的函式--這意味著你可以將相同的值傳入函式一千次,得到完全相同的結果。
結論
通過利用規則引擎模式,我們能夠將複雜的業務流程建模為一系列規則,並顯著降低我們的圈複雜度。雖然在使用設計模式時我傾向於謹慎行事,但如果您的程式碼表現出以下特徵,我會推薦這種模式:
- 大量巢狀的 if 語句
- 經常更新
- 負責提供返回值
使您的程式碼更易於使用意味著您可以以更少的摩擦來更改您的程式碼,而更少的摩擦意味著您能夠更快地交付,這有時可能是業務成功或失敗之間的區別。
相關文章
- 在Java中用規則引擎模式替代ifelse - VitaliJava模式
- 如何學習Java的規則引擎模式? - plagovJava模式Go
- URule規則引擎
- 開放封閉原則與規則引擎設計模式 - devgenius設計模式dev
- 規則引擎模式的.NET開源專案案例模式
- .NET RulesEngine(規則引擎)
- 使用DDD規格Specification模式構建資料驅動規則引擎 - jonblankenship模式
- Drools規則引擎簡介
- Java規則引擎 Easy RulesJava
- Drools 規則引擎應用
- Java各種規則引擎Java
- 架構 規則引擎 quartz架構quartz
- 決策表模式: 一種業務規則引擎實現方式模式
- 規則引擎Golang指南 – Mohit KhareGolang
- Evrete 規則引擎簡介 | baeldungVR
- 什麼是規則引擎? - martinfowler
- 快速整合和使用 drools 規則引擎
- 全渠道營銷規則引擎案例
- 什麼是業務規則引擎?
- Spring Boot + liteflow 規則引擎,太香了!Spring Boot
- 如何用Go快速實現規則引擎Go
- Drools 業務規則引擎的完整教程
- uwegeercken/jare:Java業務規則引擎(Jare)JARJava
- 規則引擎開發經驗分享 - reddit
- Devs--開源規則引擎介紹dev
- 規則引擎在IoT的重要性?
- Drools規則引擎實踐直白總結
- 用 Java 構建簡單的規則引擎Java
- 規則引擎與ML模型的比較 - xLaszlo模型
- 數字化轉型中的規則引擎
- 一個規則引擎的視覺化方案視覺化
- 第2-4-4章 規則引擎Drools規則屬性-業務規則管理系統-元件化-中臺元件化
- camunda快速入門(五):DMN規則引擎如何使用
- 風控規則引擎(一):Java 動態指令碼Java指令碼
- 基於 XAF Blazor 的規則引擎編輯器Blazor
- 規則引擎整合新的可觀測性框架框架
- Spring Boot中實現規則引擎原始碼教程Spring Boot原始碼
- .NET 6中使用Jint的JavaScript規則引擎JavaScript