经历了几次过度设计,我对一些设计模式开始抱有谨慎的戒惧。我希望能够合理地运用设计模式,而非为模式而模式。事实上,现在的我较少会有意识地运用设计模式;更多地是通过职责来驱动,以期获得合理的职责分配。之后,再通过辨别代码的坏味道,运用重构来改善设计。大多数情况下,这种方式对于设计而言,已经足够。不过,有时我们仍有必要根据具体场景,做出合理判断和决策。
我曾经遭遇的一个Story,是要实现一个Web Service,提供update的服务接口。需求要求对服务的Request进行验证。这一验证功能并非Story的核心功能,但其逻辑却比核心逻辑复杂数倍。它需要针对不同情况,遵循多个规则验证Request。这些验证逻辑如此的复杂,以至于我们可以在第一时间做出判断,它必须是单独的职责,绝对不适合放到Service对象中。
这种职责分离还不够。因为每一条验证规则,都可以视为一个单独的验证职责。若将每种验证规则封装为一个验证对象,就能很好地满足SRP。每个验证对象只需要做好自己的事情即可,结果只有两个,要么错误,要么通过。若当验证逻辑为一个整体,则可以理解为:只要违背了一条验证规则,出现错误,则可以视为验证不通过;否则将继续验证下一条规则。
乍一看来,这完全符合职责链模式的要求。我的Pair也完全同意了这一点。当我们按照这一思路进行编码时,我的疑惑随着步伐的前进,变得愈深,好像有什么东西在拽着自己,阻止我们前进。因为我们使用了Spring,故而需要将这个职责的链条通过依赖注入的方式拼接。为了迎合这种要求,标准的职责链模式需要微调。例如,我们写出这样的职责链超类:
public abstract class UpdateRequestValidator {
private UpdateRequestValidator nextValidator;
public UpdateRequestValidator(UpdateRequestValidator nextValidaor) {
this.nextValidator = nextValidator;
}
public ValidateResult validate(UpdateRequest request) {
ValidateResult result = doValidate();
if (result.isError()) {
return result;
}
return nextValidator.validate();
}
protected abstract ValidateResult doValidate();
}
|
如果仔细分析这段代码,可以发现我们是在运用装饰器模式的手法来实现职责链。这就让人感到奇怪了。由于我们自定义了构造函数,要求传入一个UpdateRequestValidator,似乎可以避免对nextValidator非空的判断。可是,链条的末尾又该放入哪个对象呢?为此,我们还定义了一个NullValidator,它什么都不做,并被要求放到职责链构造的最末尾。
够奇怪吧。这里我们对Spring的配置做出了假设,一厢情愿地要求NullValidator必须作为职责链的最后一个。可是,我们却未对这种假设做出任何约束。
与其如此别扭,还不如对设计做出调整。只要验证逻辑的职责分离与封装是合理的,我们未必一定要运用职责链模式(或者这种不伦不类的装饰器模式)。抛开这种约束后,每个Validator就可以变得更单纯,无需假设这个链条的组成;而是将这个链式逻辑转交给包含了UpdateRequestValidator列表对象的Validator对象,它可以被看成是Validator的合成对象:
public interface UpdateRequestValidator {
ValidateResult validate(UpdateRequest request);
}
public class CustomerNotFoundValidator implements UpdateRequestValidator {
@Override
public ValidateResult validate(UpdateRequest request) {
//do something;
}
}
public class CompositeUpdateRequestValidator implements UpdateRequestValidator {
private List<UpdateRequestValidator> validators;
@Required
public void setValidators(List<UpdateRequestValidator validators) {
this.validators = validators;
}
@Override
public ValidateResult validate(UpdateRequest request) {
ValidateResult result;
for (UpdateRequestValidator validator : validators) {
result = validator.validate(request);
if (result.isError()) {
return result;
}
}
return result;
}
}
|
我觉得修改后的设计意图清晰许多了,并且,它对使用者没有了隐含的约束,在Spring的ApplicationContext配置文件中,也能够很好地将我们单独实现的Validator作为List的元素配置给CompositeUpdateRequestValidator。我喜欢这样的调整。
我对装饰器模式持同样态度。正如有人说道:装饰器模式的方式有些像洋葱。洋葱的味道虽好,不过在剥洋葱时,却要小心不要被呛出眼泪。在阅读Axon Framework的源代码时,我发现一种设计方式,可以避免装饰器模式的洋葱构造,却能达到同样的装饰效果。我将其成为Interceptor模式。
需求是希望对CommandHandler对象的命令请求进行包装(或装饰),例如为命令请求提供日志或事务功能。这些功能可以是组合的。传统的方式是运用装饰器模式,例如:
public interface CommandHandler {
void handle();
}
public class LoggingCommand implements CommandHandler{
private CommandHandler commandHandler;
public LoggingCommand(CommandHandler commandHandler) {
this.commandHandler = commandHandler;
}
@Override
public void handle() {
logInfo("begin execute command");
commandHandler.handle();
logInfo("end execute command");
}
}
public class TransactionCommand implements CommandHandler {
private CommandHandler commandHandler;
public TransactionCommand(CommandHandler commandHandler) {
this.commandHandler = commandHandler;
}
@Override
public void handle() {
myTransaction.beginTransaction();
try {
commandHandler.handle();
myTransaction.commitTransaction();
} catch (Exception ex) {
myTransaction.rollbackTransaction();
}
}
}
|
假设有一个命令对象PlaceOrderCommandHandler,同时需要日志和事务功能,就需要如下构造方式:
CommandHandler command = new TransactionCommand(new LoggingCommand(new PlaceOrderCommandHandler()));
|
这种构造方式既复杂,也不容易使用Spring来注入。Axon的设计方式转变了设计视角,将这种对命令的包装,看做是对命令请求的一种拦截,这就好似AOP的pointcut一般。它定义了一个InterceptorChain,负责完成整个拦截器链条的组装,并将这些拦截职责通过Chain完成一层层的传递。设计如下:
public interface CommandHandlerInterceptor {
void handle(InterceptorChain interceptorChain);
}
public class LoggingInterceptor implements CommandHandlerInterceptor {
@Override
public void handle(InterceptorChain interceptorChain) {
info("LOG: begin");
interceptorChain.proceed();
info("LOG: end");
}
}
public class TransactionInterceptor implements CommandHandlerInterceptor {
@Override
public void handle(InterceptorChain interceptorChain) {
transaction.beginTransaction();
try {
interceptorChain.proceed();
transaction.commitTransaction();
} catch (Exception ex) {
transaction.rollbackTransaction();
}
}
}
public class InterceptorChain {
private Iterator<? extends CommandHandlerInterceptor> interceptorChain;
private CommandHandler handler;
public void proceed() {
if (interceptorChain.hasNext()) {
interceptorChain.next().handle(this);
}
handler.handle();
}
@Required
public void setHandler(CommandHandler handler) {
this.handler = handler;
}
@Required
public void setInterceptorChain(Iterator<? extends CommandHandlerInterceptor> interceptorChain) {
this.interceptorChain = interceptorChain;
}
}
|
在InterceptorChain类中,handler对象就是我们要包装(或者说拦截)的命令对象。注意,Interceptor对象的handle()方法,是将InterceptorChain对象作为参数传入的。于是就形成了如下的方法调用时序:
只要理解了InterceptorChain起到的委派传递作用,就能很好地理解这个设计。这种方式将之前装饰器模式那种洋葱式的构造转移到列表中,并通过迭代器进行迭代。同时,它将对CommandHandler以及相关拦截器对象的依赖转移到了外面,很好地支持IoC容器对依赖的注入,从而使得代码可以变得更简单,更灵活。
总之,当我们在运用设计模式时,一定要结合当前上下文,对设计模式做出合理的判断,甚至可以大胆地对GOF总结的设计模式进行大刀阔斧地挑战或修改。关键还在于把握设计模式的核心原则,并在“简单设计”的前提下,做出设计的决策。