处理复杂条件评估的设计模式

时间:2016-01-11 19:11:44

标签: java design-patterns refactoring conditional

我的目的是维护一个系统,该系统考虑三个变量的值来确定它将采取的操作。

我想重构它以使用设计模式,但找不到适合它的设计模式。

为了解释这种情况,我将以健身系统为例。

每位健身房用户都有 TYPE_OF_CONTRACT ,可能是:

  • PLATINUM_MEMBERSHIP
  • GOLD_MEMBERSHIP
  • SILVER_MEMBERSHIP

健身房有一些 GYM_CLASSES

  • WEIGHT_LIFTING
  • BODY_BALANCE
  • STEP
  • 纺纱
  • ZUMBA
  • PERSONAL_TRAINING

每位健身房用户都有 PHYSICAL_CONDITION

  • NO_RESTRICTIONS
  • OVER_65
  • LIMITED_MOBILITY
  • MEDICAL_CONDITION
  • BELOW_18

对于这三个特征的每个组合,应该执行任意一组动作。例如:

如果PLATINUM_MEMBERSHIP + PERSONAL_TRAINING + OVER_65:

  1. 需要医疗批准
  2. 签名表格
  3. 如果GOLD_MEMBERSHIP + PERSONAL_TRAINING + OVER_65:

    1. 需要医疗批准
    2. 签名表格
    3. 额外月费
    4. 如果SILVER_MEMBERSHIP + PERSONAL_TRAINING + OVER_65:

      1. 拒绝订阅
      2. if(any membership)+ STEP + MEDICAL_CONDITION:

        1. 需要医疗批准
        2. 签名表格
        3. 如果PLATINUM_MEMBERSHIP + WEIGHT_LIFTING + LIMITED_MOBILITY:

          1. 需要医疗批准
          2. 签名表格
          3. 专职工作人员协助
          4. 等等。

            特征组合可以有一组动作,这些动作不是排他性的,并不是所有组合都得到保证。

            遗留代码使用嵌套开关作为实现。例如:

            switch (contractType):
            
                case PLATINUM_MEMBERSHIP:
            
                    switch (gymClass):            
            
                        case (PERSONAL_TRAINING):
            
                            switch (physicalCondition):            
            
                                case (OVER_65):
            
                                    requiresMedicalApproval();
                                    requiresSignedForm();
            
            ...
            

            我的问题是:

            • 有3个条件组合起来定义一组规则;
            • 这些规则不一定是唯一的;
            • 并非每个组合都定义了一个集合;

            我使用提取方法技术进行了一些重构并稍微清理了一下代码,但无法摆脱3个开关。

            我希望使用设计模式来改进设计,但到目前为止我没有成功。

            我考虑过多态和策略,但无法找到使用其中任何一种的方法。

            我也在google上进行了研究,但我找不到任何可以使用的东西。

            你有什么建议?

            谢谢。

            修改

            我在研究@Paul的决策树方法时达成的解决方案。在使用决策树进行测试后,我尝试了一个三维数组,以定义规则的条件。我还使用命令模式来定义激活规则时需要执行的操作。

            简而言之:

            1)用于定义变量的枚举:

            public enum TypeOfContract { ... }
            public enum GymClasses { ... }
            public enum PhysicalCondition { ... }
            

            每个可能的条件都会放在枚举中。

            2)用于定义动作的Command接口

            public interface Command {
                public void execute(Map<String, Object> parametersMap);
            }
            

            每个动作都是Command的一个实现。 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);
                }   
            }    
            

            Procedures类表示需要执行的命令。它有一个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()]
            

            (需要ordinal()来返回对应于枚举位置的int

            要表示执行所需的操作,将关联一个Procedures类,包装要执行的操作:

            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);
                }
            

            现在需要执行的操作的所有信息都在Map中。

            由三个枚举表示的条件将传递给RulesEngine。

            RulesEngine将评估组合是否具有关联的操作,它将返回一个Procedures对象,其中包含需要执行的这些操作的列表。

            如果不是(组合没有与之关联的操作),则RulesEngine将返回一个带有空列表的有效Procedures对象。

            6)优点

            • 使用代码更清晰
            • 遗留代码的开关中的代码重复现已消失
            • 这些行动现在已经标准化并且定义明确(每个行动都在自己的班级中)
            • 现在使用的规则更容易识别(开发人员只需要查看RULES数组就知道哪些规则已设置以及每个规则会发生什么)
            • 可以轻松添加新规则和操作

            7)缺点

            • 在规则的定义中容易出错,因为它们的声明是冗长的而不是语义分析 - 它会接受重复,例如,可能会覆盖以前的定义。
            • 而不是嵌套在彼此内的3个开关,现在我有几个类。系统的维护比以前复杂一点,学习曲线稍微陡峭。
            • 程序和规则不是好名字 - 仍在寻找更好的名字; - )
            • 作为参数映射可以促进不良编码,使其与大量内容混淆。

3 个答案:

答案 0 :(得分:2)

你有多少选择?假设您每个类别有8个,也许您可​​以将特定组合表示为24位数字,每个类别8位。当您收到一组选项时,将其转换为比AND针对位掩码的位模式,以确定是否需要执行某个操作。

这仍然需要您执行测试,但至少它们不是嵌套的,并且您只需要在添加新功能时添加新测试。

答案 1 :(得分:1)

您可以使用决策树并根据值元组构建它。

这将更简单,如果正确实施甚至比硬编码条件更快,并且还提供更高的可维护性。

答案 2 :(得分:0)

在设计模式方面,如果您想降低复杂性,可以使用抽象工厂。

您可以创建三个类层次结构。

  1. TYPE_OF_CONTRACT(AbstractProductA)

    PLATINUM_MEMBERSHIP(ProductA1)

    GOLD_MEMBERSHIP(ProductA2)

    SILVER_MEMBERSHIP(ProductA3)

  2. GYM_CLASSES(AbstractProductB)

    WEIGHT_LIFTING(ProductB1)

    BODY_BALANCE(ProductB2)

    STEP(ProductB3)

    SPINNING(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)