我被设计用来维护一个系统,该系统考虑了三个变量的值以确定它将采取哪些操作。
我想重构它并使用设计模式,但是找不到适合其需求的设计模式。
为了解释这种情况,我将以健身房系统为例。
每个健身房用户都有一个合同类型(TYPE_OF_CONTRACT),可以是:
- 白金会员
- 黄金会员
- 银牌会员
健身房有一些健身课(GYM_CLASSES):
- 举重
- 平衡训练
- 踏步操
- 室内自行车训练
- 森巴舞
- 私教
每个健身房用户都有一个体质状况(PHYSICAL_CONDITION)
- 没有限制
- 超过65岁
- 行动不便
- 有医学状况
- 18岁以下
对于每种这三个特征的组合,应执行一组任意的操作。例如:
如果合同类型是白金会员+私教+超过65岁:
- 需要医疗批准
- 签署表格
如果合同类型是黄金会员+私教+超过65岁:
- 需要医疗批准
- 签署表格
- 额外月费
如果合同类型是银牌会员+私教+超过65岁:
- 拒绝订阅
如果(任意一种会员)+踏步操+有医学状况:
- 需要医疗批准
- 签署表格
如果合同类型是白金会员+举重+行动不便:
- 需要医疗批准
- 签署表格
- 指定工作人员协助
等等。
这些特征的组合可以有一组操作,这些操作不是互斥的,并且不能保证所有组合都存在。
旧代码使用嵌套开关进行实现。例如:
switch (contractType):
case PLATINUM_MEMBERSHIP:
switch (gymClass):
case (PERSONAL_TRAINING):
switch (physicalCondition):
case (OVER_65):
requiresMedicalApproval();
requiresSignedForm();
...
我的问题是:
- 有三个条件组合在一起定义了一组规则;
- 这些规则不一定是唯一的;
- 并非每个组合都定义了一组规则。
我使用提取方法技术和清理代码进行了重构,但无法摆脱3个switch语句。
我希望使用设计模式来改善设计,但到目前为止我没有成功。
我想过多态性和策略模式,但找不到使用任何一个的方法。
我还在谷歌上进行了研究,但没有找到可以使用的东西。
你有什么建议吗?
谢谢。
编辑:
在研究@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)一个Procedures类来保存每个条件所需的操作。
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类代表需要执行的命令。它有一个Command的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()方法返回枚举常量在枚举中的位置对应的整数值。
为了表示需要执行的操作,会关联一个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中。
由三个枚举表示的条件被传递给规则引擎。
规则引擎将评估组合是否有关联的操作,并返回一个程序对象,其中包含需要执行的这些操作的列表。
如果没有(组合没有与之相关的操作),则规则引擎将返回一个有效的程序对象,其中包含一个空列表。
6)优点
- 使用代码更加简洁
- 遗留代码开关中的重复代码已经消失了
- 操作现在是标准化和明确定义的(每个操作都在自己的类中)
- 使用的规则现在更容易辨识(开发人员只需要查看RULES数组就可以知道设置了哪些规则以及每个规则会发生什么)
- 可以轻松添加新规则和操作
7)缺点
- 在定义规则时很容易出错,因为它们的声明冗长且不进行语义分析-它将接受重复定义,例如可能会覆盖先前的定义。
- 现在我有几个类,而不是3个嵌套在一起的开关,因此系统的维护比以前略微复杂,学习曲线略微陡峭。
- 程序和规则不是很好的名称-仍在寻找更好的名称;-)
- 将Map作为参数可能会促使糟糕的编码,使其混杂着大量内容。