2016-01-11 88 views
3

我的目的是維護一個系統,該系統考慮三個變量的值以確定將採取哪種操作。用於處理複雜條件評估的設計模式

我想重構它使用設計模式,但找不到適合它的需求。

爲了說明這種情況,我將以健身房系統爲例進行說明。

每個健身房用戶都有一個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:

  1. 醫療審批需要
  2. 簽名的表格

如果GOLD_MEMBERSHIP + PERSONAL_TRAINING + OVER_65:

  1. 醫療審批需要
  2. 簽名的形式
  3. 額外的月租費

如果SILVER_MEMBERSHIP + PERSONAL_TRAINING + OVER_65:

  1. 拒絕訂閱

如果(任何會員)+ STEP + MEDICAL_CONDITION:

  1. 需要醫療批准
  2. 簽名的表格

如果PLATINUM_MEMBERSHIP + WEIGHT_LIFTING + LIMITED_MOBILITY:

  1. 醫療審批需要
  2. 簽名的表格
  3. 專職工作人員協助

等。

特徵的組合可以有一組操作,這些操作不是唯一的,也不是所有的組合都得到保證。

傳統代碼使用嵌套交換機作爲實現。例如:

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個開關,現在我有幾個類。系統的維護比以前複雜一點,學習曲線有點陡峭。
  • 程序和規則是不好的名字 - 仍然在尋找更好的名字;-)
  • 作爲參數映射可以促進錯誤的編碼,使它與大量的內容混亂。
+0

如果不破,不要修復它,**尤其是**,如果你甚至不知道修復它的方法。 – Kayaman

+0

首先,我會開始考慮策略和裝飾者的組合。但我的方法是在單元測試中覆蓋現有的實現,然後嘗試一種重構其中一個變量的方法。看看看起來像什麼,然後重複兩次。可能會發現你爲每一個使用不同的解決方案。 – dbugger

+0

@Kayaman,這是一個簡單的開關嵌套。我寧願不要因爲它沒有被破壞而避免處理難看的代碼。醜陋的代碼往往會隨着時間的推移而傳播,並且會使應用程序的維護成爲一場噩夢。只是因爲我不知道如何在此刻修復它,並不意味着我無法找到方法。有單元測試,我在重構之前增加了一些,現在我有一個安全網。 –

回答

1

您可以使用決策樹並從值元組構建它。

這會簡單得多,如果實施得比硬編碼條件更快,並且可以提供更高的可維護性。

2

你會有多少個選項?假設您每個類別有8個,也許您可​​以將特定組合表示爲24位數字,每個類別8位。當您收到一組選項時,將其轉換爲比位掩碼的AND位模式,以確定是否需要執行某個操作。

這仍然需要您執行測試,但至少它們不是嵌套的,並且只需在添加新功能時添加新測試。

0

在設計模式方面,如果您想降低複雜性,可以使用抽象工廠。

您可以創建三個類的層次結構。

  1. TYPE_OF_CONTRACT(AbstractProductA)

    PLATINUM_MEMBERSHIP(ProductA1)

    GOLD_MEMBERSHIP(ProductA2)

    SILVER_MEMBERSHIP(ProductA3)

  2. GYM_CLASSES(AbstractProductB)

    WE IGHT_LIFTING(ProductB1)

    BODY_BALANCE(ProductB2)

    STEP(ProductB3)

    紡絲(ProductB4)

    ZUMBA(ProductB5)

    PERSONAL_TRAINING(ProductB6)

  3. PHYSICAL_CONDITION (AbstractProductC)

    NO_RESTRICTIONS(ProductC1)

    OVER_65(ProductC2)

    LIMITED_MOBILITY(ProductC3)

    MEDICAL_CONDITION(ProductC4)

    BELOW_18(ProductC5)