
原文出自王艳涛的专栏转载请注明出处!
概述
build模式一般用来一步一步创建一个复杂对象的创建型模式,适用于初始化一个参数比较多且具有默认值的复杂对象。经过阅读下文你会发现okhttp3的OkHttpClient类是比较典型的build模式,所以本文在分析经典的build模式后,再结合okhttp3的OkHttpClient类对build模式进行分析。
经典的build模式
经典的build模式基本上包括三个部分,结合下面代码来看:
- 待创建对象的抽象基类和具体子类,如Computer类和DellComputer类;
- Builder基类和具体子类,如Builder类和DellBuilder类,执行构建,返回构建对象;
- Director类,封装构建过程。
public abstract class Computer {
protected String name;
protected String os;
protected Computer() {
}
public abstract void setName(String name);
public abstract void setOs(String os);
}
public class DellComputer extends Computer {
protected DellComputer() {
this.name = "dell-0";
this.os = "os-0";
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setOs(String os) {
this.os = os;
}
}
public abstract class Builder {
public abstract void name(String name);
public abstract void os(String os);
public abstract Computer create();
}
public class DellBuilder extends Builder{
private DellComputer dellComputer = new DellComputer();
@Override
public void name(String name) {
dellComputer.setName(name);
}
@Override
public void os(String os) {
dellComputer.setOs(os);
}
@Override
public Computer create() {
return dellComputer;
}
}
public class Director {
Builder builder = null;
public Director(Builder builder){
this.builder = builder;
}
public void construct(String name, String os){
builder.name(name);
builder.os(os);
}
}
public class Test {
public static void main(String[] args){
//构建"dell-1", "os-1"电脑
Builder builder = new DellBuilder();
Director director = new Director(builder);
director.construct("dell-1", "os-1");
Computer computer = builder.create();
}
}
优点:
具有良好的封装性,使用时不需要了解待构建对象的内部组成细节,同时各子Builder相互独立,容易扩展。
缺点:
产生多余的Builder对象和Director对象,消耗内存。
改进经典的Builder模式第一步——省去Director、实现链式调用
在实际应用时,一般情况下,Director对象可以省去,直接使用Builder对象进行构建,同时将Builder对象的每个构建方法的返回值设为自身,这样就可以实现链式调用。
在上述代码的基础上,删除Director类,将Builder类和DellBuilder类修改为如下代码:
public abstract class Builder {
public abstract Builder name(String name);//修改返回值为本身
public abstract Builder os(String os);//修改返回值为本身
public abstract Computer create();
}
public class DellBuilder extends Builder{
private DellComputer dellComputer = new DellComputer();
@Override
public Builder name(String name) {
dellComputer.setName(name);
return this;//返回自身
}
@Override
public Builder os(String os) {
dellComputer.setOs(os);
return this;//返回自身
}
@Override
public Computer create() {
return dellComputer;
}
}
public class Test {
public static void main(String[] args){
//构建"dell-1", "os-1"电脑
Builder builder = new DellBuilder();
//链式调用
Computer computer = builder.name("dell-1").os("os-1").create();
}
}
改进经典Builder模式第二步——将具体的Builder作为待构建对象的内部类
将具体的Builder作为待构建对象的内部类可以进一步的减少类的文件数,增强内聚性。
在第一步改进的基础上,删除Builder类和DellBuilder类,DellComputer类代码改进如下:
public class DellComputer extends Computer {
protected DellComputer() {
this.name = "dell-0";
this.os = "os-0";
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setOs(String os) {
this.os = os;
}
//Builder作为DellComputer的内部类
public static class Builder{
//持有一个DellComputer实例
Computer computer = new DellComputer();
public Builder(){}
public Builder name(String name){
computer.setName(name);
return this;//返回自身
}
public Builder os(String os){
computer.setOs(os);
return this;//返回自身
}
public Computer create(){
return computer;//返回实例
}
}
}
public class Test {
public static void main(String[] args){
//构建"dell-1", "os-1"电脑
//链式调用
Computer computer = new DellComputer.Builder()
.name("dell-1").os("os-1").create();
}
}
okhttp3中OkhttpClient类的Builder模式
okhttp3中OkhttpClient类的Builder模式基本上与第二步改进后的Builder模式类似,唯一不同的是OkhttpClient类和其中的Builder内部类都分别增加了一个构造方法,可以相互以对方为参数初始化自己,语言表述有点绕,一看代码相信很快就理解了。
OkhttpClient类的代码量比较大,但是大部分都是属性和赋值方法,在这里只保留其中一个属性connectTimeout和其赋值方法,将其他的删除,不影响分析其Builder设计模式,整理后的OkhttpClient类代码如下所示(保留关键代码)。
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
//属性
final int connectTimeout;
//向外暴露的构造函数
public OkHttpClient() {
this(new Builder());//返回Builder实例
}
//用Builder实例初始化OkHttpClient属性
OkHttpClient(Builder builder) {
this.connectTimeout = builder.connectTimeout;
}
//为属性赋值
public int connectTimeoutMillis() {
return connectTimeout;
}
//返回一个经过OkHttpClient实例初始化后的Builder实例
public Builder newBuilder() {
return new Builder(this);
}
//内部类Builder
public static final class Builder {
int connectTimeout;
//构造函数
public Builder() {
connectTimeout = 10_000;
}
//通过OkHttpClient实例初始化自己
Builder(OkHttpClient实例初始化自己 okHttpClient) {
this.connectTimeout = okHttpClient.connectTimeout;
}
//构建方法
public Builder connectTimeout(long timeout, TimeUnit unit) {
connectTimeout = checkDuration("timeout", timeout, unit);
return this;
}
//启动构建,返回经过自己初始化后的OkHttpClient实例
public OkHttpClient build() {
return new OkHttpClient(this);
}
}
}
获取OkHttpClient实例的用法:
分析上述代码,就可以得出开发时获取OkHttpClient实例的用法,分为两种情况:
- 使用默认配置,不修改OkHttpClient的属性时,获取默认OkHttpClient实例代码如下:
- 需要自定义配置,获取配置后的OkHttpClient实例。
第一种情况下,获取默认OkHttpClient实例代码如下,在调用构造函数时,用Builder的默认属性值初始化自己。
其中方法1是通过暴露能够根据Builder初始化自己的构造函数实现的,是okhttp官方给出的便捷获取默认OkHttpClient实例的方法。
方法2可以理解为标准的Builder模式获取OkHttpClient实例的方法。
但是既然有便捷方法1,所以在获取默认OkHttpClient实例时还是推荐使用方法1。
OkHttpClient okHttpClient = new OkHttpClient();//方法1
//或者使用下面的方法
OkHttpClient okHttpClient = new OkHttpClient.Builder().build;//方法2
第二种情况下,类似第一种情况下的方法2。
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(1000, TimeUnit.SECONDS).build;
总结
本文从经典的Builder模式入手,分析了一般情况下Builder的改进实现方式,同时又结合了okhttp3中OkHttpClient类,对Builder模式的实现进一步分析。
总之,如果一个类的属性特别多,有默认值,构造时特别麻烦,那么别犹豫,Builder模式当仁不让。同时根据项目自身的情况,决定采取哪一种形式的Builder模式,是经典模式还是改进一或者改进二,再或者使用OkHttpClient类的方法,再或者基于此的其他形式,毕竟灵活、方便、解耦、方便阅读才是使用设计模式的最终目的,不必特别拘泥于形式。
网友评论