我的目的是维护一个系统,该系统考虑三个变量的值来确定它将采取的操作。
我想重构它以使用设计模式,但找不到适合它的设计模式。
为了解释这种情况,我将以健身系统为例。
每位健身房用户都有 TYPE_OF_CONTRACT ,可能是:
健身房有一些 GYM_CLASSES :
每位健身房用户都有 PHYSICAL_CONDITION
对于这三个特征的每个组合,应该执行任意一组动作。例如:
如果PLATINUM_MEMBERSHIP + PERSONAL_TRAINING + OVER_65:
如果GOLD_MEMBERSHIP + PERSONAL_TRAINING + OVER_65:
如果SILVER_MEMBERSHIP + PERSONAL_TRAINING + OVER_65:
if(any membership)+ 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个开关。
我希望使用设计模式来改进设计,但到目前为止我没有成功。
我考虑过多态和策略,但无法找到使用其中任何一种的方法。
我也在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)优点
7)缺点
答案 0 :(得分:2)
你有多少选择?假设您每个类别有8个,也许您可以将特定组合表示为24位数字,每个类别8位。当您收到一组选项时,将其转换为比AND
针对位掩码的位模式,以确定是否需要执行某个操作。
这仍然需要您执行测试,但至少它们不是嵌套的,并且您只需要在添加新功能时添加新测试。
答案 1 :(得分:1)
您可以使用决策树并根据值元组构建它。
这将更简单,如果正确实施甚至比硬编码条件更快,并且还提供更高的可维护性。
答案 2 :(得分:0)
在设计模式方面,如果您想降低复杂性,可以使用抽象工厂。
您可以创建三个类层次结构。
TYPE_OF_CONTRACT(AbstractProductA)
PLATINUM_MEMBERSHIP(ProductA1)
GOLD_MEMBERSHIP(ProductA2)
SILVER_MEMBERSHIP(ProductA3)
GYM_CLASSES(AbstractProductB)
WEIGHT_LIFTING(ProductB1)
BODY_BALANCE(ProductB2)
STEP(ProductB3)
SPINNING(ProductB4)
ZUMBA(ProductB5)
PERSONAL_TRAINING(ProductB6)
PHYSICAL_CONDITION(AbstractProductC)
NO_RESTRICTIONS(ProductC1)
OVER_65(ProductC2)
LIMITED_MOBILITY(ProductC3)
MEDICAL_CONDITION(ProductC4)
BELOW_18(ProductC5)