读者盆友早上好。从今天开始,我们系列化的介绍设计模式。
先打响设计模式第一枪:Builder模式。笔者的博客目的在于通过实战、实际的例子介绍设计模式,而不是空洞的介绍理论,看了也不怎么清楚到底怎么用。
学习方法:个人建议《Design Patterns》和《Effective Java》的第二条+《大话设计模式》结合起来理解。
结论:
典型示例用法:
自测接口一般用Postman,如果要在代码中发送请求呢?有很多方法,OkHttpClient就可以实现在代码中发送各种请求,它就是用到Builder模式的典型之一。
//获取默认客户端对象
private static OkHttpClient getDefaultHttpClient() {
OkHttpClient ok = new OkHttpClient();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
ConnectionPool connectionPool = new ConnectionPool(16, 5, TimeUnit.MINUTES);
builder.connectionPool(connectionPool);
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequests(64);
dispatcher.setMaxRequestsPerHost(16);
builder.dispatcher(dispatcher);
builder.connectTimeout(3, TimeUnit.SECONDS);
builder.readTimeout(0, TimeUnit.SECONDS);
builder.writeTimeout(3, TimeUnit.SECONDS);
return builder.build();
}
private static OkHttpClient getDefaultHttpClientV2() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequests(64);
dispatcher.setMaxRequestsPerHost(16);
return builder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES))
.dispatcher(dispatcher).connectTimeout(3, TimeUnit.SECONDS).readTimeout(0, TimeUnit.SECONDS).writeTimeout(3, TimeUnit.SECONDS).build();
}
//发送一个请求
Response response = httpClient.newCall(request).execute();
以上就是典型的Builder模式。同样Request、Header都是使用这个模式。
好处:
1、流式API
2、具名的可选参数
3、构造与表示分离
这么说还是抽象,其实《Effective java》的第二条把优点很清楚了,这里不再赘述。
我们今天用OkHttpClient来举例。
//1.Builder本质是一个静态内部类
public final class OkHttpClient implements Cloneable, Call.Factory {
...
public OkHttpClient() {
this(new Builder());
}
private OkHttpClient(Builder builder) {...}
public static final class Builder {
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
}
public Builder connectTimeout(long timeout, TimeUnit unit) {...}
public Builder readTimeout(long timeout, TimeUnit unit) {...}
public Builder writeTimeout(long timeout, TimeUnit unit) {...}
}
}
这就是一个Builder模式非常典型的写法。
核心有以下几点:
看一下OKOkHttpClient
初始化大量参数用构造方法一般这么做:
Person p = new Person();
p.setHead("Head");
p.setArm("Arm");
p.setBody("Body");
....
这么做存在的问题是什么?
1、你需要知道要设置哪些参数,如果不知道就会遗漏
2、一大推的set,写起来就是一坨“样本代码”
OkHttpClient就有大量默认参数需要初始化,人家默认帮你初始化好,你创建你的Builder即可,保证你不会遗漏关键属性,同时又专注你所要设置的属性。
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
}
以上就是OkHttpClient的Builder默认初始化的属性,想一想,如果让你一个一个set的话,真是无聊透顶,而且你还得学习所有的属性,以便都理解后才能赋值。
为避免用户需要知道很多无需知道的细节,现在很简单,我们只用这么做即可。
return builder.connectionPool(new ConnectionPool(16, 5, TimeUnit.MINUTES))
.dispatcher(dispatcher)
.connectTimeout(3, TimeUnit.SECONDS)
.readTimeout(0, TimeUnit.SECONDS)
.writeTimeout(3, TimeUnit.SECONDS).build();
代码非常简洁,这就是流式API。和java8中的Stream写法类似。
teamList.stream()
.filter(e->e.getIsLeader==true)
.map(TeamListDTO::getId)
.collect(toList());
这里的具名参数是相对“重叠构造器”(telescoping constructor)而言.如果属性不多,用带不同参数的构造器实例化很好,多了就非常不方便,而且属性有先后顺序且属性名只有类型,赋值容易错。
《Effective Java》第二条说的非常清晰了,不在赘述。
OkHttpClient中的Request、Header都是类似的用法
如果我们自己写,就这么做
//第1步:定义一个final 的class类
类似这样
public final class Request {
}
//第2步:把构造方法私有化,对外提供一个获取Builder的公有方法
类似这样:
private Request(Builder builder) {}
public Builder newBuilder() {
return new Builder(this);
}
好处:外部只能通过Builder来获取一个实例,怎么获取呢?
//第3步:构造静态内部类Builder
类似这样:
public static class Builder {
}
//第4步:静态内部类Builder的构造方法
public Builder() {
//无参构造方法初始化各种属性
}
private Builder(Request request) {
//私有化有参构造方法,让你不能通过参数实例化
}
//第5步:类似set方法,构造可以流式赋值的返回builder的各个参数的赋值方法
类似这样:
public Builder url(HttpUrl url) {
if (url == null) throw new IllegalArgumentException("url == null");
this.url = url;
return this;
}
public Builder header(String name, String value) {
headers.set(name, value);
return this;
}
这个模式个人觉得实际开发中需要自己写的场景挺少的,只要知道是这种设计模式,怎么用、大体优缺点即可。