在一个case 中需要写这样一段代码, 写一个Spring的bean 来解析HttpServletRequest的传递的command 信息,command 的名字作为后台request 记录的键值。需要写入的代码类如下:
@Bean
public class CommandParser implements CommandParser<HttpServletRequest> {
private static final String CMDKEY = "CMDKEY";
public boolean accept(HttpServletRequest request) {
// write your code here
return false;
}
public String getCommand(HttpServletRequest request) {
String callName = (String) request.getAttribute(CMDKEY);
request.removeAttribute(CMDKEY);
return callName;
}
}
需求
This command parser need resolve a command name and if it can resolve the command name , then set command name into request attribute with key CMDKEY, then return true,otherwise, do nothing, return false;
It will try to resolve two type of calls, the first from ‘/api‘ , the call name is from header, the header key is ‘X-API-CALL-NAME‘ the other one is from ‘/websvc‘, the value is from url parameter , the parameter key is ‘callname‘.
Write a logic in accept method to
例子
这个需求并不难,在code review 的时候发现大部分的思路是下面这样的
@Bean
public class CommandParser implements CommandParser<HttpServletRequest> {
private static final String CMDKEY = "CMDKEY";
private static final String HEADERKEY = "X-API-CALL-NAME";
private static final String CALLNAME = "callname";
private static final String AP_SUFFIX = "/api";
private static final String WEBSVC_SUFFIX = "/ws/websvc";
public boolean accept(HttpServletRequest request) {
String uri = request.getRequestURI();
String cmdName = null;
if (uri.endsWith(API_DLL_SUFFIX)){
cmdName = request.getHeader(HEADERKEY);
} else if (uri.endsWith(WEBSVC_SUFFIX)){
cmdName = request.getParameter(CALLNAME);
}
if (cmdName != null){
request.setAttribute(CMDKEY, cmdName);
return true;
}
return false;
}
public String getCommand(HttpServletRequest request) {
String callName = (String) request.getAttribute(CMDKEY);
request.removeAttribute(CMDKEY);
return callName;
}
}
这个写法本身的逻辑并没有错,但是从代码的角度有没有更好的写法哪?
分析
这个需求实际上包含两个维度, 一个维度是request URI, 一个维度是要做的动作。 如下表所示。
我们写程序的时候实际上可以从两个维度来切分。一个是从request URI 的切面来进行切分,一个是从动作来进行切分。 从上文的代码来看大家的切分是从命令这个层面进行切分。从表上看就是水平切分。

那我们想,如果从另外垂直切面进行切分,即从request URI 方向进行。代码的表现是什么样。

代码:
@Bean
public class CommandParser implements CommandParser<HttpServletRequest> {
private static final String CMDKEY = "CMDKEY";
private static final String HEADERKEY = "X-API-CALL-NAME";
private static final String CALLNAME = "callname";
private static final String AP_SUFFIX = "/api";
private static final String WEBSVC_SUFFIX = "/ws/websvc";
public boolean accept(HttpServletRequest request) {
return trySetCmdNameByApidll(request) || trySetCmdNameByWebsvc(request);
}
private boolean trySetCmdNameByApidll(HttpServletRequest request) {
String cmdName = null;
if (request.getRequestURI().contains(API_DLL_SUFFIX)
&& (cmdName = request.getHeader(HEADERKEY)) != null) {
request.setAttribute(CMDKEY, cmdName);
}
return cmdName != null;
}
private boolean trySetCmdNameByWebsvc(HttpServletRequest request) {
String cmdName = null;
if (request.getRequestURI().contains(WEBSVC_SUFFIX)
&& (cmdName = request.getParameter(CALLNAME)) != null) {
request.setAttribute(CMDKEY, cmdName);
}
return cmdName != null;
}
public String getCommand(HttpServletRequest request) {
String callName = (String) request.getAttribute(CMDKEY);
request.removeAttribute(CMDKEY);
return callName;
}
}
这两种写法都是可以正常工作的,但是如果二选一,你会选择哪一种?大家投票后,发现更多的人喜欢垂直切面的代码。有些人说,虽然说不出来,但是感觉第二种会更好一点。
那么我们就比较一下这两种写法,从需求上看,动作维度是固定的,就是两种, 但是URI
的维度可能是变化的。大家都知道软件的开闭原则,但是开闭原则的应用的方向是对变化的对象应用,而不是对固定的对象应用。因此我们要对URI的变化修改说No,而欢迎URI的扩展说Yes.
在第一个逻辑中,加入出现一个新的需求,如果header 出现一个OperationName,如果出现就优先从个OperationName中取,那么代码很有可能写成如下:
@Bean
public class CommandParser implements CommandParser<HttpServletRequest> {
private static final String CMDKEY = "CMDKEY";
private static final String HEADERKEY = "X-API-CALL-NAME";
private static final String CALLNAME = "callname";
private static final String AP_SUFFIX = "/api";
private static final String WEBSVC_SUFFIX = "/ws/websvc";
public boolean accept(HttpServletRequest request) {
String uri = request.getRequestURI();
String cmdName = null;
cmdName = request.getHeader(“OperationName”);
if(cmdName !=null){
return true;
}
String uri = request.getRequestURI();
String cmdName = null;
if (uri.endsWith(API_DLL_SUFFIX)){
cmdName = request.getHeader(HEADERKEY);
} else if (uri.endsWith(WEBSVC_SUFFIX)){
cmdName = request.getParameter(CALLNAME);
}
if (cmdName != null){
request.setAttribute(CMDKEY, cmdName);
return true;
}
return false;
}
public String getCommand(HttpServletRequest request) {
String callName = (String) request.getAttribute(CMDKEY);
request.removeAttribute(CMDKEY);
return callName;
}
}
这个逻辑有可越加越多,到了后期,比如有个URI 需要根据条件来进行OperationName的读取,这个逻辑就很难剥离。而第二种写法所有的URI的逻辑都是内聚的,任何逻辑的变化都是发生在URI的逻辑内部,这完全符合开闭原则。
有的人问:看起来只有resolveCommand 在不同URI下是变化的。如果我不完全垂直切分,而是在resolveCommand垂直分, 在setAttribute 水平切分怎么样?

那这样的代码我们可以看看变成什么样:
代码:
@Bean
public class CommandParser implements CommandParser<HttpServletRequest> {
private static final String CMDKEY = "CMDKEY";
private static final String CALLNAME = "callname";
private static final String API_DLL_SUFFIX = "/api.dll";
private static final String WEBSVC_SUFFIX = "/ws/websvc";
public boolean accept(HttpServletRequest request) {
String cmdName = resolveAPIName(request);
if (cmdName == null) {
return false;
} else {
request.setAttribute(TRADINGAPICALLNAME, cmdName);
return true;
}
}
public class CommandName {
private String cmdName;
public String getCmdName() {
return cmdName;
}
public void setCmdName(String cmdName) {
this.cmdName = cmdName;
}
}
private boolean tryResolveCmdNameByApidll(HttpServletRequest request, CommandName cmdName) {
String cmdName = null;
if (request.getRequestURI().contains(API_DLL_SUFFIX)
&& (cmdName = request.getHeader(HEADERKEY)) != null) {
cmdName.seCmdName(cmdName)
}
return cmdName != null;
}
private boolean trySetCmdNameByWebsvc(HttpServletRequest request) {
String cmdName = null;
if (request.getRequestURI().contains(WEBSVC_SUFFIX)
&& (cmdName = request.getParameter(CALLNAME)) != null) {
cmdName.seCmdName(cmdName);
}
return cmdName != null;
}
private String resolveAPIName(HttpServletRequest request) {
CommandName cmd = new CommandName();
if (!trySetApidllCmd(request, cmd) && !trySetWebsvcCmd(request, cmd)) {
return null;
}
return cmd.getCmdName();
}
public String getCommand(HttpServletRequest request) {
String callName = (String) request.getAttribute(CMDKEY);
request.removeAttribute(CMDKEY);
return callName;
}
}
你看到这样的逻辑不仅不简单,而且更复杂了。由于是单例的Bean,考虑多线程的数据传递,不得不创造一个类来进行传递command。这样不仅每个线程多创造了一个对象,而且代码逻辑也并没有得到改善。
总结:
简单逻辑的代码编写,如果仔细考虑,也是有不同的写法。目的都是一致的, 但是方向不同导致代码的不同。看起来很一致,但是内在的思想完全不一样。总体来说,一定要进行对代码的元素进行分析,开闭原则要应用在变化的部分。这样才能让代码看起来更舒服。
原文:https://www.cnblogs.com/developernotes/p/14676411.html