首页 > 其他 > 详细

代码扩展的方向

时间:2021-04-19 14:41:52      阅读:10      评论:0      收藏:0      [点我收藏+]


在一个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

  • return false, if there is no such command in request
  • return true , if a command is resolved, and put in request attribute(request.setAttribute(CMDKEY, cmdName))

例子

这个需求并不难,在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

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!