鱼C论坛

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

[技术交流] 用builder代替setget

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

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

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

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

在《代码整洁之道》中,方法参数是禁止超过3个的,超过3个人阅读起来非常不舒服,这里已经6个了。《代码整洁之道》提倡把超过3个参数的封装到类中。也就是说我们可以把httpPost方法中的6个参数抽成这样:
public static String httpPost(HttpPostParam param){
  //...此处继续省略一万个字
}

那HttpPostParam自然就是对6个参数的setget了:
public class HttpPostParam{
    private String url;
    private String json;
    private String charset;
    private Integer socketTimeout;
    private Integer connectTimeOut;
    private Map<String,String> headers;

    public String getUrl() {
        return url;
    }

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

    public String getJson() {
        return json;
    }

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

    public String getCharset() {
        return charset;
    }

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

    public Integer getSocketTimeout() {
        return socketTimeout;
    }

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

    public Integer getConnectTimeOut() {
        return connectTimeOut;
    }

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

    public Map<String, String> getHeaders() {
        return headers;
    }

    public void setHeaders(Map<String, String> headers) {
        this.headers = headers;
    }
}

可以说这是我们最常用的方法了,用setget为属性进行赋值。但是在使用该类的时候,我们需要new一个对象然后挨个set方法调用,如果我们的参数更多,那书写起来更不方便,看着也不舒服,我们的代码将会被大量的set方法调用所污染:
@Test
public void testHttpPost(){
    HttpPostParam param=new HttpPostParm();
    param.setUrl("http://www.baidu.com");
    param.setJson("{'test':'test'}");
    param.setCharset("UTF-8");
    param.setSocketTimeout(1000);
    param.setConnectTimeOut(1000);
    Map<String,String> headers=new HashMap<>();
    headers.put("Connection","Keep-Alive");
    param.setHeaders(headers);

    HttpClientUtil.httpPost(param);//实际调用方法就一行
}

有人可能会说给HttpPostParam加入构造函数把参数传进去,尤其是可以使用编译器的快捷键自动生成带参数的构造函数:
public HttpPostParam(String url, String json, String charset, Integer socketTimeout, Integer connectTimeOut, Map<String, String> headers) {//构造函数参数依然看着这么累
        this.url = url;
        this.json = json;
        this.charset = charset;
        this.socketTimeout = socketTimeout;
        this.connectTimeOut = connectTimeOut;
        this.headers = headers;
    }

  public HttpPostParam(String url, String json, String charset, Integer socketTimeout, Integer connectTimeOut) {//如果有的参数不用还得写重载
        this.url = url;
        this.json = json;
        this.charset = charset;
        this.socketTimeout = socketTimeout;
        this.connectTimeOut = connectTimeOut;
    }
这样调用起来自然很方便:
@Test
public void testHttpPost(){
    Map<String,String> headers=new HashMap<>();
    headers.put("Connection","Keep-Alive");
    param.setHeaders(headers);

    HttpClientUtil.httpPost(new HttpPostParm(
        "http://www.baidu.com",
        "{'test':'test'}",
        "UTF-8",
        1000,
        1000,
        headers
    ));//实际调用方法就一行
}
但是,当这样参数加减可就要重新生成构造函数了。
于是想想之前用的google公司开源的protobuf用到的builder,那也来试试吧!
首先模仿下protobuf的Builder使用方法:
@Test
public void testHttpPost(){
    Map<String,String> headers=new HashMap<>();
    headers.put("Connection","Keep-Alive");
    param.setHeaders(headers);

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

整体来说都很和谐,接下来看看实现:
pulic class HttpPostParam{
    private String url;
    private String json;
    private String charset;
    private Integer socketTimeout;
    private Integer connectTimeOut;
    private Map<String,String> headers;

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

    static class Builder {
        private String url;
        private String json;
        private String charset;
        private Integer socketTimeout;
        private Integer connectTimeOut;
        private Map<String, String> headers;

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

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

        public Builder setJson(String json) {
            this.json = json;
            return this;
        }

        public Builder setCharset(String charset) {
            this.charset = charset;
            return this;
        }

        public Builder setSocketTimeout(Integer socketTimeout) {
            this.socketTimeout = socketTimeout;
            return this;
        }

        public Builder setConnectTimeOut(Integer connectTimeOut) {
            this.connectTimeOut = connectTimeOut;
            return this;
        }

        public Builder setHeaders(Map<String, String> headers) {
            this.headers = headers;
            return this;
        }
    }

    public String getUrl() {
        return url;
    }

    public String getJson() {
        return json;
    }

    public String getCharset() {
        return charset;
    }

    public Integer getSocketTimeout() {
        return socketTimeout;
    }

    public Integer getConnectTimeOut() {
        return connectTimeOut;
    }

    public Map<String, String> getHeaders() {
        return headers;
    }
}

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

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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