Java Builder模式(设计模式之Builder模式)

1.设计模式之终极结合

昨天项目上线(今年是没有新需求了,那提前祝大家新年快乐),上班继续最近几期博客的编写。承接上一篇博客观察者模式,这一期我们一起来了解一下Builder模式。我们最近几期博客打算主要讲一下单例模式、观察者模式、Build模式,目的是为了方便后期为大家带来RXJava+Retrofit2.0+Okhttp3结合使用的网络请求框架。

思考了一上午,我该怎么写出Builder模式的精华呢?(如果你们有一定要告诉我)

2.Builder模式定义

简单说就是:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

3.Builder模式理解

看了定义觉得难以理解,那么说说我的理解。比如说你需要构造一个机器:是不是会包含很多部件,但是根据相同的制造过程可以制作出不同的机器(机器猫,机器狗,机器人,机器*等等)。再比如说相同的注册流程可以注册出不同的公司(百度,华为,小米等等),不同的注册信息可以表示出不同的公司。

然后我们再说说为什么要用Builder模式吧,比如你做个机器人,我们是不是要用很多参数(手,脚,关节,眼睛,嘴巴,鼻子等等)来构造,如果你用一般的构造方法,你觉得你要写多少个构造方法呢?显得特别麻烦,代码还不清晰,传参也麻烦;这样我们就引入了Builder模式。

4.Builder模式简单示例

步骤一:

创建一个实体类CompanyClient,里面有个和实体类(只能获取变量值)相同变量的静态内部类Builder(设置变量值) 。在实体类通过构造函数来构造一个Builder,通过调用build()来建造一个具体的实体对象。

/**
* 实体类 包含一个静态内部类 Builder
*/
public class CompanyClient {
public final String companyName;
public final String companyAddress;
public final double companyRegfunds;
public final String mPerson;
public final String mType;
//构造方法
public CompanyClient() {
this(new Builder());
}
//构造方法
public CompanyClient(Builder builder){
this.companyName = builder.companyName;
this.companyAddress = builder.companyAddress;
this.companyRegfunds = builder.companyRegfunds;
this.mPerson = builder.person;
this.mType = builder.type;
}
public String getCompanyName() {
return companyName;
}
public String getCompanyAddress() {
return companyAddress;
}
public double getCompanyRegfunds() {
return companyRegfunds;
}
public String getmPerson() {
return mPerson;
}
public String getmType() {
return mType;
}
public Builder newBuilder() {
return new Builder(this);
}
@Override
public String toString() {
return "CompanyClient{" +
"companyName='" + companyName + '\'' +
", companyAddress='" + companyAddress + '\'' +
", companyRegfunds=" + companyRegfunds +"千万"+
", mPerson=" + mPerson +
", mType='" + mType + '\'' +
'}';
}
/**
*静态内部类 Builder
*/
public static class Builder{
public String companyName;
public String companyAddress;
public double companyRegfunds;
public String person;
public String type;
//构造方法
public Builder() {
companyName = companyName;
companyAddress = companyAddress;
companyRegfunds = companyRegfunds;
person = person;
type = type;
}
//构造方法
Builder(CompanyClient companyClient){
this.companyName = companyClient.companyName;
this.companyAddress = companyClient.companyAddress;
this.companyRegfunds = companyClient.companyRegfunds;
this.person = companyClient.mPerson;
this.type = companyClient.mType;
}
public Builder setCompanyName(String name) {
companyName = name;
return this;
}
public Builder setCompanyAddress(String address) {
companyAddress = address;
return this;
}
public Builder setCompanyRegfunds(double regfunds) {
companyRegfunds = regfunds;
return this;
}
public Builder setmPerson(String per) {
person = per;
return this;
}
public Builder setmType(String typeStr) {
type = typeStr;
return this;
}
//构建一个实体
public CompanyClient build() {
return new CompanyClient(this);
}
}
}

步骤二:实现类(TestBuilder)

public class TestBuilder {
public static void main(String[] args) {
CompanyClient client = new CompanyClient.Builder()
.setCompanyName("百度")
.setCompanyAddress("海定区百度大厦")
.setCompanyRegfunds(5)
.setmPerson("1000人以上")
.build();
System.out.println("构造出一个公司:" + client.toString());
System.out.println("---------------------分隔符");
CompanyClient.Builder builder = new CompanyClient.Builder();
builder.setCompanyName("华为");
builder.setCompanyAddress("海定区百度大厦");
builder.setCompanyRegfunds(20);
builder.setmType("通信科技行业");
CompanyClient client1 = builder.build();
System.out.println("构造出另一个公司:" + client1.toString());
}
}

dos命令运行输出结果:

构造出一个公司:CompanyClient{companyName='百度', companyAddress='海定区百度大厦', companyRegfunds=5.0千万, mPerson=1000人以上, mType='null'}
---------------------分隔符
构造出另一个公司:CompanyClient{companyName='华为', companyAddress='海定区百度大厦', companyRegfunds=20.0千万, mPerson=null, mType='通信科技行业'}

5.Builder模式库拓展

用到Builder模式的有ImageLoader,Glide,Okhttp,Retrofit以及Android AlertDialog 等等;今天我们就以现在运用最广泛的Okhttp做简单分析:

先来看一下Okhttp的注释

/**
* Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their
* responses.
*
* <h3>OkHttpClients should be shared</h3>
*
* <p>OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for
* all of your HTTP calls. This is because each client holds its own connection pool and thread
* pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a
* client for each request wastes resources on idle pools.
*
* <p>Use {@code new OkHttpClient()} to create a shared instance with the default settings:
* <pre> {@code
*
* // The singleton HTTP client.
* public final OkHttpClient client = new OkHttpClient();
* }</pre>
*
* <p>Or use {@code new OkHttpClient.Builder()} to create a shared instance with custom settings:
* <pre> {@code
*
* // The singleton HTTP client.
* public final OkHttpClient client = new OkHttpClient.Builder()
* .addInterceptor(new HttpLoggingInterceptor())
* .cache(new Cache(cacheDir, cacheSize))
* .build();
* }</pre>
*
* <h3>Customize your client with newBuilder()</h3>
*
* <p>You can customize a shared OkHttpClient instance with {@link #newBuilder()}. This builds a
* client that shares the same connection pool, thread pools, and configuration. Use the builder
* methods to configure the derived client for a specific purpose.
*
* <p>This example shows a call with a short 500 millisecond timeout: <pre> {@code
*
* OkHttpClient eagerClient = client.newBuilder()
* .readTimeout(500, TimeUnit.MILLISECONDS)
* .build();
* Response response = eagerClient.newCall(request).execute();
* }</pre>
*/
/**以上是okhttp的部分注释,大概说的就是:这个工程被用于网络访问,被返回相应的结果;
*我们在使用它的时候需要使用单例模式来保证整个网络请求中只存在一个实例,从而减少访问时间以及节约内存。
*您可以使用{@link#newBuilder()}自定义共享的okhtp客户端实例。这构建了一个共享相同连接池、线程池
*和配置的客户端。使用Builder方法来达到配置定制的客户端。
*/

来看一下okhttp的部分源码(删减版)

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
final Dispatcher dispatcher;
final Proxy proxy;
final List<Protocol> protocols;
final List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors;
final List<Interceptor> networkInterceptors;
final ProxySelector proxySelector;
final CookieJar cookieJar;
final Cache cache;
final InternalCache internalCache;
final SocketFactory socketFactory;
final SSLSocketFactory sslSocketFactory;
final CertificateChainCleaner certificateChainCleaner;
final HostnameVerifier hostnameVerifier;
final CertificatePinner certificatePinner;
final Authenticator proxyAuthenticator;
final Authenticator authenticator;
final ConnectionPool connectionPool;
final Dns dns;
final boolean followSslRedirects;
final boolean followRedirects;
final boolean retryOnConnectionFailure;
final int connectTimeout;
final int readTimeout;
final int writeTimeout;
final int pingInterval;
//构造函数 通过静态内部类构造函数Builder()传入值
public OkHttpClient() {
this(new Builder());
}
//构造函数
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
this.proxySelector = builder.proxySelector;
this.cookieJar = builder.cookieJar;
this.cache = builder.cache;
this.internalCache = builder.internalCache;
this.socketFactory = builder.socketFactory;
boolean isTLS = false;
for (ConnectionSpec spec : connectionSpecs) {
isTLS = isTLS || spec.isTls();
}
if (builder.sslSocketFactory != null || !isTLS) {
this.sslSocketFactory = builder.sslSocketFactory;
this.certificateChainCleaner = builder.certificateChainCleaner;
} else {
X509TrustManager trustManager = systemDefaultTrustManager();
this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
}
this.hostnameVerifier = builder.hostnameVerifier;
this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
certificateChainCleaner);
this.proxyAuthenticator = builder.proxyAuthenticator;
this.authenticator = builder.authenticator;
this.connectionPool = builder.connectionPool;
this.dns = builder.dns;
this.followSslRedirects = builder.followSslRedirects;
this.followRedirects = builder.followRedirects;
this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
this.connectTimeout = builder.connectTimeout;
this.readTimeout = builder.readTimeout;
this.writeTimeout = builder.writeTimeout;
this.pingInterval = builder.pingInterval;
}
/** Default connect timeout (in milliseconds). */
public int connectTimeoutMillis() {
return connectTimeout;
}
/** Default read timeout (in milliseconds). */
public int readTimeoutMillis() {
return readTimeout;
}
/** Default write timeout (in milliseconds). */
public int writeTimeoutMillis() {
return writeTimeout;
}
/** Web socket ping interval (in milliseconds). */
public int pingIntervalMillis() {
return pingInterval;
}
public Proxy proxy() {
return proxy;
}
public ProxySelector proxySelector() {
return proxySelector;
}
public CookieJar cookieJar() {
return cookieJar;
}
public Cache cache() {
return cache;
}
InternalCache internalCache() {
return cache != null ? cache.internalCache : internalCache;
}
public Dns dns() {
return dns;
}
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
public Builder newBuilder() {
return new Builder(this);
}
public static final class Builder {
Dispatcher dispatcher;
Proxy proxy;
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
ProxySelector proxySelector;
CookieJar cookieJar;
Cache cache;
InternalCache internalCache;
SocketFactory socketFactory;
SSLSocketFactory sslSocketFactory;
CertificateChainCleaner certificateChainCleaner;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator;
Authenticator authenticator;
ConnectionPool connectionPool;
Dns dns;
boolean followSslRedirects;
boolean followRedirects;
boolean retryOnConnectionFailure;
int connectTimeout;
int readTimeout;
int writeTimeout;
int pingInterval;
//构造
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;
pingInterval = 0;
}
//构造
Builder(OkHttpClient okHttpClient) {
this.dispatcher = okHttpClient.dispatcher;
this.proxy = okHttpClient.proxy;
this.protocols = okHttpClient.protocols;
this.connectionSpecs = okHttpClient.connectionSpecs;
this.interceptors.addAll(okHttpClient.interceptors);
this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
this.proxySelector = okHttpClient.proxySelector;
this.cookieJar = okHttpClient.cookieJar;
this.internalCache = okHttpClient.internalCache;
this.cache = okHttpClient.cache;
this.socketFactory = okHttpClient.socketFactory;
this.sslSocketFactory = okHttpClient.sslSocketFactory;
this.certificateChainCleaner = okHttpClient.certificateChainCleaner;
this.hostnameVerifier = okHttpClient.hostnameVerifier;
this.certificatePinner = okHttpClient.certificatePinner;
this.proxyAuthenticator = okHttpClient.proxyAuthenticator;
this.authenticator = okHttpClient.authenticator;
this.connectionPool = okHttpClient.connectionPool;
this.dns = okHttpClient.dns;
this.followSslRedirects = okHttpClient.followSslRedirects;
this.followRedirects = okHttpClient.followRedirects;
this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure;
this.connectTimeout = okHttpClient.connectTimeout;
this.readTimeout = okHttpClient.readTimeout;
this.writeTimeout = okHttpClient.writeTimeout;
this.pingInterval = okHttpClient.pingInterval;
}
/**
* Sets the default connect timeout for new connections. A value of 0 means no timeout,
* otherwise values must be between 1 and {@link Integer#MAX_VALUE} when converted to
* milliseconds.
*/
public Builder connectTimeout(long timeout, TimeUnit unit) {
connectTimeout = checkDuration("timeout", timeout, unit);
return this;
}
/**
* Sets the default read timeout for new connections. A value of 0 means no timeout, otherwise
* values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds.
*/
public Builder readTimeout(long timeout, TimeUnit unit) {
readTimeout = checkDuration("timeout", timeout, unit);
return this;
}
/**
* Sets the default write timeout for new connections. A value of 0 means no timeout, otherwise
* values must be between 1 and {@link Integer#MAX_VALUE} when converted to milliseconds.
*/
public Builder writeTimeout(long timeout, TimeUnit unit) {
writeTimeout = checkDuration("timeout", timeout, unit);
return this;
}
/**
* Sets the interval between web socket pings initiated by this client. Use this to
* automatically send web socket ping frames until either the web socket fails or it is closed.
* This keeps the connection alive and may detect connectivity failures early. No timeouts are
* enforced on the acknowledging pongs.
*
* <p>The default value of 0 disables client-initiated pings.
*/
public Builder pingInterval(long interval, TimeUnit unit) {
pingInterval = checkDuration("interval", interval, unit);
return this;
}
private static int checkDuration(String name, long duration, TimeUnit unit) {
if (duration < 0) throw new IllegalArgumentException(name + " < 0");
if (unit == null) throw new NullPointerException("unit == null");
long millis = unit.toMillis(duration);
if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException(name + " too large.");
if (millis == 0 && duration > 0) throw new IllegalArgumentException(name + " too small.");
return (int) millis;
}
/**
* Sets the HTTP proxy that will be used by connections created by this client. This takes
* precedence over {@link #proxySelector}, which is only honored when this proxy is null (which
* it is by default). To disable proxy use completely, call {@code setProxy(Proxy.NO_PROXY)}.
*/
public Builder proxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
/**
* Sets the proxy selection policy to be used if no {@link #proxy proxy} is specified
* explicitly. The proxy selector may return multiple proxies; in that case they will be tried
* in sequence until a successful connection is established.
*
* <p>If unset, the {@link ProxySelector#getDefault() system-wide default} proxy selector will
* be used.
*/
public Builder proxySelector(ProxySelector proxySelector) {
this.proxySelector = proxySelector;
return this;
}
/**
* Sets the handler that can accept cookies from incoming HTTP responses and provides cookies to
* outgoing HTTP requests.
*
* <p>If unset, {@linkplain CookieJar#NO_COOKIES no cookies} will be accepted nor provided.
*/
public Builder cookieJar(CookieJar cookieJar) {
if (cookieJar == null) throw new NullPointerException("cookieJar == null");
this.cookieJar = cookieJar;
return this;
}
/** Sets the response cache to be used to read and write cached responses. */
void setInternalCache(InternalCache internalCache) {
this.internalCache = internalCache;
this.cache = null;
}
/** Sets the response cache to be used to read and write cached responses. */
public Builder cache(Cache cache) {
this.cache = cache;
this.internalCache = null;
return this;
}
/**
* Sets the DNS service used to lookup IP addresses for hostnames.
*
* <p>If unset, the {@link Dns#SYSTEM system-wide default} DNS will be used.
*/
public Builder dns(Dns dns) {
if (dns == null) throw new NullPointerException("dns == null");
this.dns = dns;
return this;
}
public Builder addNetworkInterceptor(Interceptor interceptor) {
networkInterceptors.add(interceptor);
return this;
}
//通过build()来建造一个OkHttpClent实例
public OkHttpClient build() {
return new OkHttpClient(this);
}
}
}

** 我们来看看怎么使用: **

直接使用Okhttp

public final mOkHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url("your url")
.addHeader("","")
.post(requestBody)
.build();
Response response = mOkHttpClient.newCall(request).execute();

Retrofit+okhttp结合使用

//Retrofit+okhttp,支持Rxjava,gson解析
public class RetrofitHelper {
private static OkHttpClient mOkHttpClient;
private RetrofitHelper() {
initOkHttpClient();
}
/**
* 静态内部类,实例化对象使用
*/
private static class SingleRetrofitHelper {
private static final RetrofitHelper INSTANCE = new RetrofitHelper();
}
/**
* 对外唯一实例的接口
*
* @return
*/
public static RetrofitHelper getInstance() {
return SingleRetrofitHelper.INSTANCE;
}
public ShopkeeperApi getApi() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Config.BASE_URL)
.client(mOkHttpClient)
//支持gson解析
.addConverterFactory(GsonConverterFactory.create())
//支持rxjava
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
return retrofit.create(ShopkeeperApi.class);
}
/**
* 初始化OKHttpClient 这就是DCL模式下通过Builder模式来实例化一个OkHttpClient
*/
private static void initOkHttpClient(){
//日志过滤
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
if (mOkHttpClient == null) {
synchronized (RetrofitHelper.class) {
if (mOkHttpClient == null) {
mOkHttpClient = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.retryOnConnectionFailure(true)
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.build();
}
}
}
}
}

6.Builder模式总结

优点:我可以不必知道你的内部构造是怎样的,我可以直接使用Builder建造自己需要的客户端;代码清晰,易维护,易扩展;将构造和表示分离,降低耦合
缺点:代码也可能不清晰,不易维护(怎么说:比如你的客户端实现了很多接口,当你每当修改接口的时候,每次都要对应修改你的客户端);使用不恰当消耗内存
大概就说这么多吧,期待接下来的Retrofit+okhttp+RXJava来实现网络请求。

原文链接:https://blog.csdn.net/qq_17678217/article/details/86507693

posted @   Lafite-1820  阅读(546)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示