我的目的是維護一個系統,該系統考慮三個變量的值以確定將採取哪種操作。用於處理複雜條件評估的設計模式
我想重構它使用設計模式,但找不到適合它的需求。
爲了說明這種情況,我將以健身房系統爲例進行說明。
每個健身房用戶都有一個TYPE_OF_CONTRACT,這可能是:
- PLATINUM_MEMBERSHIP
- GOLD_MEMBERSHIP
- SILVER_MEMBERSHIP
健身房有一些GYM_CLASSES:
- WEIGHT_LIFTING
- BODY_BALANCE
- STEP
- SPINNING
- ZUMBA
- PERSONAL_TRAINING
每個健身房用戶都有一個PHYSICAL_CONDITION
-
個
- NO_RESTRICTIONS
- OVER_65
- LIMITED_MOBILITY
- MEDICAL_CONDITION
- BELOW_18
對於這三種特徵的每種組合,動作的任意的設定應當被執行。例如:
如果PLATINUM_MEMBERSHIP + PERSONAL_TRAINING + OVER_65:
- 醫療審批需要
- 簽名的表格
如果GOLD_MEMBERSHIP + PERSONAL_TRAINING + OVER_65:
- 醫療審批需要
- 簽名的形式
- 額外的月租費
如果SILVER_MEMBERSHIP + PERSONAL_TRAINING + OVER_65:
- 拒絕訂閱
如果(任何會員)+ STEP + MEDICAL_CONDITION:
- 需要醫療批准
- 簽名的表格
如果PLATINUM_MEMBERSHIP + WEIGHT_LIFTING + LIMITED_MOBILITY:
- 醫療審批需要
- 簽名的表格
- 專職工作人員協助
等。
特徵的組合可以有一組操作,這些操作不是唯一的,也不是所有的組合都得到保證。
傳統代碼使用嵌套交換機作爲實現。例如:
switch (contractType):
case PLATINUM_MEMBERSHIP:
switch (gymClass):
case (PERSONAL_TRAINING):
switch (physicalCondition):
case (OVER_65):
requiresMedicalApproval();
requiresSignedForm();
...
我的問題是:
- 有3個條件,結合以限定一組規則;
- 這些規則不一定是唯一的;
- 並非每個組合都定義一個集合;
我重構了一點使用提取方法技術和清理代碼一點,但無法擺脫3開關。
我希望使用設計模式來改進設計,但到目前爲止我沒有成功。
我想過多態性和策略,但無法想出一個方法來使用它們中的任何一個。
我也研究過谷歌,但還沒有找到任何我可以使用的。
你有什麼建議?
謝謝。
編輯:
一個解決方案,我達到了,而研究@保羅的決策樹方法。在用決策樹測試之後,我嘗試了一個三維數組,來定義規則的條件。我還使用命令模式來定義在規則激活時需要執行的操作。
簡單:
1)枚舉定義變量:
public enum TypeOfContract { ... }
public enum GymClasses { ... }
public enum PhysicalCondition { ... }
每種可能的情況將在枚舉放。
2)命令接口來定義的行動
public interface Command {
public void execute(Map<String, Object> parametersMap);
}
每一個動作將是指令的實現。 Map參數將用於將運行時上下文傳遞給方法。
3)一個程序類來保存每個條件所需的操作。
public class Procedures {
private List<Command> actionsToExecute = new LinkedList<Command>();
public static final Procedures NO_ACTIONS_TO_EXECUTE = new Procedures();
private Procedures() {}
public Procedures(Command... commandsToExecute) {
if (commandsToExecute == null || commandsToExecute.length == 0) {
throw new IllegalArgumentException("Procedures must have at least a command for execution.");
}
for (Command command : commandsToExecute) {
actionsToExecute.add(command);
}
}
public List<Command> getActionsToExecute() {
return Collections.unmodifiableList(this.actionsToExecute);
}
}
程序類表示需要執行的命令。它有一個命令的LinkedList,以確保命令以所需的順序執行。
如果三個變量的組合不存在,它將發送NO_ACTIONS_TO_EXECUTE而不是null。
4)一種RulesEngine類,要註冊
public class RulesEngine {
private static final int NUMBER_OF_FIRST_LEVEL_RULES = TypeOfContract.values().length;
private static final int NUMBER_OF_SECOND_LEVEL_RULES = GymClasses.values().length;
private static final int NUMBER_OF_THIRD_LEVEL_RULES = PhysicalCondition.values().length;
private static final Procedures[][][] RULES =
new Procedures[NUMBER_OF_FIRST_LEVEL_RULES]
[NUMBER_OF_SECOND_LEVEL_RULES]
[NUMBER_OF_THIRD_LEVEL_RULES];
{ //static block
RULES
[TypeOfContract.PLATINUM_MEMBERSHIP.ordinal()]
[GymClasses.PERSONAL_TRAINING.ordinal()]
[PhysicalCondition.OVER_65.ordinal()] =
new Procedures(new RequireMedicalApproval(),
new RequireSignedForm());
RULES
[TypeOfContract.GOLD_MEMBERSHIP.ordinal()]
[GymClasses.PERSONAL_TRAINING.ordinal()]
[PhysicalCondition.OVER_65.ordinal()] =
new Procedures(new RequireMedicalApproval(),
new RequireSignedForm(),
new AddExtraMonthlyFee());
...
}
private RulesEngine() {}
public static Procedures loadProcedures(TypeOfContract TypeOfContract,
GymClasses GymClasses, PhysicalCondition PhysicalCondition) {
Procedures procedures = RULES
[TypeOfContract.ordinal()]
[GymClasses.ordinal()]
[PhysicalCondition.ordinal()];
if (procedures == null) {
return Procedures.NO_ACTIONS_TO_EXECUTE;
}
return procedures;
}
}
(異常代碼格式化用於可視化在這個網站上的緣故)
這裏變量的有意義的關聯進行定義的規則和其命令在RULES三維數組中。
規則是通過使用相應的枚舉來定義的。
對於我給第一個例子中,PLATINUM_MEMBERSHIP + PERSONAL_TRAINING + OVER_65,以下將適用:
RULES
[TypeOfContract.PLATINUM_MEMBERSHIP.ordinal()]
[GymClasses.PERSONAL_TRAINING.ordinal()]
[PhysicalCondition.OVER_65.ordinal()]
(序()需要返回對應於該枚舉的位置中的int)
爲了表示演出,一個程序類相關聯的所需的行動,包裝要被執行的動作:
new Procedures(new RequireMedicalApproval(), new RequireSignedForm());
RequireMedicalApproval和RequireSignedForm都實現了Command接口。
定義變量這個組合將是整條生產線:
RULES
[TypeOfContract.PLATINUM_MEMBERSHIP.ordinal()]
[GymClasses.PERSONAL_TRAINING.ordinal()]
[PhysicalCondition.OVER_65.ordinal()] =
new Procedures(new RequireMedicalApproval(),
new RequireSignedForm());
要檢查的特定組合具有與之關聯的行動中,loadProcedures
被調用,通過代表特定組合的枚舉。
5)使用
Map<String, Object> context = new HashMap<String, Object>();
context.put("userId", 123);
context.put("contractId", "C45354");
context.put("userDetails", userDetails);
context.put("typeOfContract", TypeOfContract.PLATINUM_MEMBERSHIP);
context.put("GymClasses", GymClasses.PERSONAL_TRAINING);
context.put("PhysicalCondition", PhysicalCondition.OVER_65);
...
Procedures loadedProcedures = RulesEngine.loadProcedures(
TypeOfContract.PLATINUM_MEMBERSHIP,
GymClasses.PERSONAL_TRAINING,
PhysicalCondition.OVER_65);
for (Command action : loadedProcedures.getActionsToExecute()) {
action.equals(context);
}
所有信息的行爲需要執行的現在地圖內。
由三個枚舉表示的條件傳遞給RulesEngine。
RulesEngine將評估組合是否具有關聯的操作,它將返回一個具有需要執行的操作列表的Procedures對象。
如果沒有(該組合沒有與其關聯的操作),RulesEngine將返回一個有效的程序對象和一個空列表。
6)優點
- 用法代碼是乾淨得多
- 的代碼在傳統代碼的開關的複製已經消失現在
- 的動作被標準化和良好定義(每一個在其自己的類)
- 現在使用的規則更容易辨別(開發人員只需要看RULES數組,以知道哪些規則設置以及每個規則會發生什麼)
- 新的規則和操作可以輕鬆地添加
7)缺點
- 容易在規則的定義錯誤,因爲他們的聲明是冗長而不是語義分析 - 它會接受重複例如,可能會覆蓋以前的定義。
- 而不是互相嵌套的3個開關,現在我有幾個類。系統的維護比以前複雜一點,學習曲線有點陡峭。
- 程序和規則是不好的名字 - 仍然在尋找更好的名字;-)
- 作爲參數映射可以促進錯誤的編碼,使它與大量的內容混亂。
如果不破,不要修復它,**尤其是**,如果你甚至不知道修復它的方法。 – Kayaman
首先,我會開始考慮策略和裝飾者的組合。但我的方法是在單元測試中覆蓋現有的實現,然後嘗試一種重構其中一個變量的方法。看看看起來像什麼,然後重複兩次。可能會發現你爲每一個使用不同的解決方案。 – dbugger
@Kayaman,這是一個簡單的開關嵌套。我寧願不要因爲它沒有被破壞而避免處理難看的代碼。醜陋的代碼往往會隨着時間的推移而傳播,並且會使應用程序的維護成爲一場噩夢。只是因爲我不知道如何在此刻修復它,並不意味着我無法找到方法。有單元測試,我在重構之前增加了一些,現在我有一個安全網。 –