Command模式是GOF中较为简单的,用来封装行为的一个模式。在我们初涉设计模式的领域前,我们可能就在不知不觉中使用了它。比如说JAVA多线程中的Ruuable接口,比如说swing编程中用于处理事件的action,这些通通都是Command模式的使用。跟很多行为型模式一样,command模式用于降低接收者和发送者的耦合,我们经常可以在一些开源框架中看到,command实例对象常在层与层之间进行传递,接收者对于接收到的command,根本不知道其所能处理的业务,只知道如何调用它来执行。
举我们常见的业务框架的处理来讲,每一个command对应于一个业务处理类,这些command统一在一个Front Controller中进行调用。
Controller中调用command的方法:
#controller ..... //获取command对象 //调用command command.execute(ctx); ....
而在我们的业务处理类即command中,我们会去调用service层的方法来获取对应的数据,并返回给页面处理。这个是一般的流程,而我们在处理过程中,我们可能会遇到多种不同的异常(一个command中需要实现的业务可能需要调用多个不同service来完成),而在command中,我们处理异常的方式大多是将异常信息记录下来,然后返回给client端一个友好的错误信息提示,或者直接抛给Front Controller进行统一的处理。如果command这一层的开发与service层的开发是由不同的人员进行的,那么对于service层抛出的异常,还需要先进行封装成command层的异常(这个也就是我们所说的异常链的转化),再抛给Front Controller进行处理。
#controller try{ ..... //获取command对象 //调用command command.execute(ctx); .... }catch(xxxBizException ex) { //记录异常信息 logger.warn("xxx",ex); //构造一个友好的response }
当我们现实世界中的业务远不会这么简单,我们经常需要根据不同异常来构造不同的友好提示信息,如果我们把这些不同的异常都放到controller进行处理,那么每次当command需要增加新的异常处理时,都需要对controller进行修改,而且,从设计层面上讲,这些业务异常的处理是属于controller的职责吗?在我看来,controller只需要处理一些通用的异常即可,对于那些可变而且跟业务强依赖的异常,还是需要放到command自己本身来处理,但是,这样一来,command就变成了下面这个样子:
#ListingGoodCommand public void execute(Context ctx) throws Throwable { ... try{ //校验权限 Authenticator.validate(ctx.getUser); }catch(UserNotFoundException ex) { //记录异常 logger.warn(ex); //构造用户不存在的信息 ctx.setError("user not found"); //直接返回,不执行后续的操作 return; }catch(UserExpiredException ex) //记录异常 logger.warn(ex); //构造用户过期的信息 ctx.setError("user is expired."); //直接返回,不执行后续的操作 return; } //获取所有的商品 try{ response=GoodService.listAll(ctx.getUser); }catch(WebServiceException ex) { //记录异常 logger.warn(ex); //出现webservice异常 ctx.setError("Can not acquier goods now."); //直接返回,不执行后续的操作 return; } //其他操作 ... }
从上可以看出,我们的command代码可读性很差,而且不易扩展。有没有什么好的方式能够统一处理异常但又不放在controller中呢?有。只需要修改下ICommand接口以及controller类即可。
ICommand接口增加一个postProcess()方法:
Controller类中增加调用ICommand#postProcess()的处理:
#controller try{ //保存发生的异常 Throwable t=null; ..... //获取command对象 //调用command command.execute(ctx); .... }catch(xxxBizException1 ex) { //记录异常信息 logger.warn("xxx",ex); //构造一个友好的response } catch(xxxBizException2 ex) { //记录异常信息 logger.warn("xxx",ex); //构造一个友好的response }catch(Throwable ex) { //保存未能进行处理的异常 t=ex; } //如果有未能处理的异常,则调用ICommand#postProcess()处理 if(t!=null) command. postProcess(ctx,t);
从上可以看出,在controller的异常处理中,我们把xxxBizException1,xxxBizException2 都当作是通用的异常进行处理,剩下的都会转发到command中去进行处理。这样,我们的ListingGoodCommand就演变成了:
#ListingGoodCommand public void execute(Context ctx) throws Throwable { ... //校验权限 Authenticator.validate(ctx.getUser); //获取所有的商品 response=GoodService.listAll(ctx.getUser); //其他操作 ... } public void postProcess(Context ctx,Throwable t) { if(t instanceof UserNotFoundException) { //记录异常 logger.warn(ex); //构造用户不存在的信息 ctx.setError("user not found"); } if(t instanceof UserExpiredException) { //记录异常 logger.warn(ex); //构造用户过期的信息 ctx.setError("user is expired."); } //其他异常同理 }
这样一来,我们就能够在同一个地方进行异常的处理,并且我们command中的业务逻辑变得很清晰。看到这里,眼尖的同学可能已经发现,这实际上就是Command的后Filter模式。