鱼C论坛

 找回密码
 立即注册
查看: 3200|回复: 1

[技术交流] 用builder代替setget

[复制链接]
发表于 2017-1-14 22:58:09 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
讲设计模式的大多都很枯燥,而且都用一些比较专业的术语去描述设计模式的角色。GOF的《设计模式》更吓人,推荐一本叫做《大话设计模式》的书籍,把设计模式讲的很有趣,配图和故事都很萌。前两天重构公司代码的一个http client的二次封装类,众所周知的是http请求参数很多,除了正常的请求url、参数之外还有http头部信息,这些东西放请求方法的参数里简直没法看:
  1. public static String httpPost(String url,String json,String charset,int socketTimeout,int connectTimeOut,Map<String,String> headers){
  2.     //...此处省略一万个字
  3. }
复制代码


在《代码整洁之道》中,方法参数是禁止超过3个的,超过3个人阅读起来非常不舒服,这里已经6个了。《代码整洁之道》提倡把超过3个参数的封装到类中。也就是说我们可以把httpPost方法中的6个参数抽成这样:

  1. public static String httpPost(HttpPostParam param){
  2.   //...此处继续省略一万个字
  3. }
复制代码


那HttpPostParam自然就是对6个参数的setget了:

  1. public class HttpPostParam{
  2.     private String url;
  3.     private String json;
  4.     private String charset;
  5.     private Integer socketTimeout;
  6.     private Integer connectTimeOut;
  7.     private Map<String,String> headers;

  8.     public String getUrl() {
  9.         return url;
  10.     }

  11.     public void setUrl(String url) {
  12.         this.url = url;
  13.     }

  14.     public String getJson() {
  15.         return json;
  16.     }

  17.     public void setJson(String json) {
  18.         this.json = json;
  19.     }

  20.     public String getCharset() {
  21.         return charset;
  22.     }

  23.     public void setCharset(String charset) {
  24.         this.charset = charset;
  25.     }

  26.     public Integer getSocketTimeout() {
  27.         return socketTimeout;
  28.     }

  29.     public void setSocketTimeout(Integer socketTimeout) {
  30.         this.socketTimeout = socketTimeout;
  31.     }

  32.     public Integer getConnectTimeOut() {
  33.         return connectTimeOut;
  34.     }

  35.     public void setConnectTimeOut(Integer connectTimeOut) {
  36.         this.connectTimeOut = connectTimeOut;
  37.     }

  38.     public Map<String, String> getHeaders() {
  39.         return headers;
  40.     }

  41.     public void setHeaders(Map<String, String> headers) {
  42.         this.headers = headers;
  43.     }
  44. }
复制代码


可以说这是我们最常用的方法了,用setget为属性进行赋值。但是在使用该类的时候,我们需要new一个对象然后挨个set方法调用,如果我们的参数更多,那书写起来更不方便,看着也不舒服,我们的代码将会被大量的set方法调用所污染:

  1. @Test
  2. public void testHttpPost(){
  3.     HttpPostParam param=new HttpPostParm();
  4.     param.setUrl("http://www.baidu.com");
  5.     param.setJson("{'test':'test'}");
  6.     param.setCharset("UTF-8");
  7.     param.setSocketTimeout(1000);
  8.     param.setConnectTimeOut(1000);
  9.     Map<String,String> headers=new HashMap<>();
  10.     headers.put("Connection","Keep-Alive");
  11.     param.setHeaders(headers);

  12.     HttpClientUtil.httpPost(param);//实际调用方法就一行
  13. }
复制代码


有人可能会说给HttpPostParam加入构造函数把参数传进去,尤其是可以使用编译器的快捷键自动生成带参数的构造函数:

  1. public HttpPostParam(String url, String json, String charset, Integer socketTimeout, Integer connectTimeOut, Map<String, String> headers) {//构造函数参数依然看着这么累
  2.         this.url = url;
  3.         this.json = json;
  4.         this.charset = charset;
  5.         this.socketTimeout = socketTimeout;
  6.         this.connectTimeOut = connectTimeOut;
  7.         this.headers = headers;
  8.     }

  9.   public HttpPostParam(String url, String json, String charset, Integer socketTimeout, Integer connectTimeOut) {//如果有的参数不用还得写重载
  10.         this.url = url;
  11.         this.json = json;
  12.         this.charset = charset;
  13.         this.socketTimeout = socketTimeout;
  14.         this.connectTimeOut = connectTimeOut;
  15.     }
复制代码

这样调用起来自然很方便:

  1. @Test
  2. public void testHttpPost(){
  3.     Map<String,String> headers=new HashMap<>();
  4.     headers.put("Connection","Keep-Alive");
  5.     param.setHeaders(headers);

  6.     HttpClientUtil.httpPost(new HttpPostParm(
  7.         "http://www.baidu.com",
  8.         "{'test':'test'}",
  9.         "UTF-8",
  10.         1000,
  11.         1000,
  12.         headers
  13.     ));//实际调用方法就一行
  14. }
复制代码

但是,当这样参数加减可就要重新生成构造函数了。
于是想想之前用的google公司开源的protobuf用到的builder,那也来试试吧!
首先模仿下protobuf的Builder使用方法:

  1. @Test
  2. public void testHttpPost(){
  3.     Map<String,String> headers=new HashMap<>();
  4.     headers.put("Connection","Keep-Alive");
  5.     param.setHeaders(headers);

  6.     HttpClientUtil.httpPost(
  7.         HttpPostParam.Builder.newBuilder().
  8.         .setUrl("http://www.baidu.com")
  9.         .setJson("{'test':'test'}")
  10.         .setCharset("UTF-8")
  11.         .setSocketTimeout(1000)
  12.         .setConnectTimeOut(1000)
  13.         .setHeaders(headers)
  14.         .build()
  15.     )); //这似乎好读了一些,直接build构建对象,同时不要的属性可以不用set,不会像构造函数那样需要重载好几个版本
  16. }
复制代码


整体来说都很和谐,接下来看看实现:

  1. pulic class HttpPostParam{
  2.     private String url;
  3.     private String json;
  4.     private String charset;
  5.     private Integer socketTimeout;
  6.     private Integer connectTimeOut;
  7.     private Map<String,String> headers;

  8.     //④构造函数中把builder传进来,把builder中赋过值的属性逐一赋值给成员变量,这样对于外部调用者直接get就拿到值了
  9.     private HttpPostParam(Builder builder) {
  10.         this.url = builder.url;
  11.         this.json = builder.json;
  12.         this.charset = builder.charset;
  13.         this.socketTimeout = builder.socketTimeout;
  14.         this.connectTimeOut = builder.connectTimeOut;
  15.         this.headers = builder.headers;
  16.     }

  17.     static class Builder {
  18.         private String url;
  19.         private String json;
  20.         private String charset;
  21.         private Integer socketTimeout;
  22.         private Integer connectTimeOut;
  23.         private Map<String, String> headers;

  24.         //①创建个builder对象
  25.         public static Builder newBuilder() {
  26.             return new Builder();
  27.         }
  28.         //③最后创建HttpPostParam对象并且把builder自己传给HttpPostParam的构造函数
  29.         public HttpPostParam build() {
  30.             return new HttpPostParam(this);
  31.         }

  32.         //②对builder中的成员进行赋值。同时返回this,这样做到链式编程。其他的成员属性相同,略。
  33.         public Builder setUrl(String url) {
  34.             this.url = url;
  35.             return this;
  36.         }

  37.         public Builder setJson(String json) {
  38.             this.json = json;
  39.             return this;
  40.         }

  41.         public Builder setCharset(String charset) {
  42.             this.charset = charset;
  43.             return this;
  44.         }

  45.         public Builder setSocketTimeout(Integer socketTimeout) {
  46.             this.socketTimeout = socketTimeout;
  47.             return this;
  48.         }

  49.         public Builder setConnectTimeOut(Integer connectTimeOut) {
  50.             this.connectTimeOut = connectTimeOut;
  51.             return this;
  52.         }

  53.         public Builder setHeaders(Map<String, String> headers) {
  54.             this.headers = headers;
  55.             return this;
  56.         }
  57.     }

  58.     public String getUrl() {
  59.         return url;
  60.     }

  61.     public String getJson() {
  62.         return json;
  63.     }

  64.     public String getCharset() {
  65.         return charset;
  66.     }

  67.     public Integer getSocketTimeout() {
  68.         return socketTimeout;
  69.     }

  70.     public Integer getConnectTimeOut() {
  71.         return connectTimeOut;
  72.     }

  73.     public Map<String, String> getHeaders() {
  74.         return headers;
  75.     }
  76. }
复制代码


可以看到HttpPostParam被分成两层,外层只有get方法用来获取属性,而内层构造了个静态内部类叫Builder。通过Builder的newBuilder来创建Builder对象,然后调用Builder中的set方法对属性进行赋值,需要注意的是set方法返回了builder本身,这样在外部调用的时候就可以做到链式编程,即:setUrl().setHeaders()...,在赋值之后使用对外提供的build方法把当前对象直接通过构造参数传给HttpPostParam的实例,HttpPostParam的构造函数中对外层的HttpPostParam成员变量进行了赋值。
整体流程其实不复杂,总体来说Builder模式会比单纯的setget方法构造出的类要复杂一些而且代码量多,但是要比单纯的setget和构造函数传参要灵活可变,把可变性进行了封装,api可读性强,操作也简单了一些。
另外,如果需要一些必要的参数,可以直接在Builder的newBuilder中指出:

  1. //其他的代码略,这里只给出newBuilder的写法
  2. public static Builder newBuilder(String url) {
  3.     this.url=url;
  4.     return new Builder();
  5. }
复制代码

builder模式在设计模式中属于建造类型的模式,相对来说比较简单。推荐使用builder模式代替通常的bean或entity的setget方法。

评分

参与人数 1鱼币 +5 贡献 +2 收起 理由
零度非安全 + 5 + 2 感谢楼主无私奉献!

查看全部评分

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2017-1-15 23:45:08 | 显示全部楼层
前来沾光,我也来学习学习
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-3-29 15:59

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表