1.前言
在设计模式里,建造者模式你可能听起来有点陌生,但是一提到Builder模式,你可能就会稍微有点印象。这
个印象可能并不是来源于你曾经写过Builder模式。而是在平常编程的时候,总会碰到一个xxx.Builder()类。
这个Builder类就是我们经常在无意中用到的Builder模式,也成为建造者模式。
2.常见的Builder模式
我们总会在无意中用到一些Builder模式,你可能现在想不起来,那么我可以举几个例子稍微提醒一下你!
Retrofit
(这里面有的参数是我自己封装的类。你只需知道,Retofit的构建是通过,自身的Builder类来构造的就行)Retrofit.png
OkHttpClient
OkHttpClient.png
AlertDialog
AlertDialog.png
3.Builder模式实例
OkHttpClient源码
这下是不是有了点印象。如果你稍加追究就会发现,无论是Retrofit,还是OkHttpClient亦或是AlertDialog,他们都有一个共同的特点。就是都有一个Builder类。看到这你可能在想,这不是废话嘛。哈哈的确是废话,不过这几个都有一个共同的特点就是,他们的构造方法都不是public修饰的而是protect修饰的。而唯一能够构造返回他们本身对象的就是他们各自Builder类重的build()或者create()方法。而我说的是否正确呢。下面分别上图或者源码证明一下!
/** * OkHttpClient源码 */public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory { final int connectTimeout; final int readTimeout; final int writeTimeout; final int pingInterval; public OkHttpClient() { this(new Builder()); } OkHttpClient(Builder builder) { this.connectTimeout = builder.connectTimeout; this.readTimeout = builder.readTimeout; this.writeTimeout = builder.writeTimeout; this.pingInterval = builder.pingInterval; } //中间省略... public static final class Builder { int connectTimeout; int readTimeout; int writeTimeout; int pingInterval; public Builder() { connectTimeout = 10_000; readTimeout = 10_000; writeTimeout = 10_000; pingInterval = 0; } Builder(OkHttpClient okHttpClient) { this.connectTimeout = okHttpClient.connectTimeout; this.readTimeout = okHttpClient.readTimeout; this.writeTimeout = okHttpClient.writeTimeout; this.pingInterval = okHttpClient.pingInterval; } public Builder connectTimeout(long timeout, TimeUnit unit) { connectTimeout = checkDuration("timeout", timeout, unit); return this; } public Builder readTimeout(long timeout, TimeUnit unit) { readTimeout = checkDuration("timeout", timeout, unit); return this; } public Builder writeTimeout(long timeout, TimeUnit unit) { writeTimeout = checkDuration("timeout", timeout, unit); return this; } public Builder pingInterval(long interval, TimeUnit unit) { pingInterval = checkDuration("interval", interval, unit); return this; } public OkHttpClient build() { return new OkHttpClient(this); } } }
这里我把OkHttpClient的源码,缩减了一下,只留下了4个参数。让我们来看一下OkHttp框架是怎么创建实例的。
他的构造方法是public。但是不能设置参数。一旦使用了
OkHttpClient okHttpClient=new OkHttpClient();
那么它内部的参数,都是默认的,无法通过okHttpClient这个实例来设置和修改参数。
我们用OkHttpClient.Builder构建实例
OkHttpClient.Builder builder=new OkHttpClient.Builder(); OkHttpClient okHttpClient=builder .readTimeout(5*1000, TimeUnit.SECONDS) .writeTimeout(5*1000, TimeUnit.SECONDS) .connectTimeout(5*1000, TimeUnit.SECONDS) .build();
使用Builder创建实例的时候,不但可以链式结构,还可以修改参数(原因是因为,Builder类中的源码,每>个方法的返回值都是Builder本身)
Retrofit源码
这里我们在举一个Retrofit的例子(代码也会稍微简化)
public final class Retrofit { private final Map<Method, ServiceMethod<?, ?>> serviceMethodCache = new ConcurrentHashMap<>(); final okhttp3.Call.Factory callFactory; final HttpUrl baseUrl; final List<Converter.Factory> converterFactories; final List<CallAdapter.Factory> adapterFactories; final @Nullable Executor callbackExecutor; final boolean validateEagerly; Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl, List<Converter.Factory> converterFactories, List<CallAdapter.Factory> adapterFactories, @Nullable Executor callbackExecutor, boolean validateEagerly) { this.callFactory = callFactory; this.baseUrl = baseUrl; this.converterFactories = unmodifiableList(converterFactories); // Defensive copy at call site. this.adapterFactories = unmodifiableList(adapterFactories); // Defensive copy at call site. this.callbackExecutor = callbackExecutor; this.validateEagerly = validateEagerly; } public Builder newBuilder() { return new Builder(this); } public static final class Builder { private final Platform platform; private @Nullable okhttp3.Call.Factory callFactory; private HttpUrl baseUrl; private final List<Converter.Factory> converterFactories = new ArrayList<>(); private final List<CallAdapter.Factory> adapterFactories = new ArrayList<>(); private @Nullable Executor callbackExecutor; private boolean validateEagerly; Builder(Platform platform) { this.platform = platform; converterFactories.add(new BuiltInConverters()); } public Builder() { this(Platform.get()); } Builder(Retrofit retrofit) { platform = Platform.get(); callFactory = retrofit.callFactory; baseUrl = retrofit.baseUrl; converterFactories.addAll(retrofit.converterFactories); adapterFactories.addAll(retrofit.adapterFactories); // Remove the default, platform-aware call adapter added by build(). adapterFactories.remove(adapterFactories.size() - 1); callbackExecutor = retrofit.callbackExecutor; validateEagerly = retrofit.validateEagerly; } public Builder client(OkHttpClient client) { return callFactory(checkNotNull(client, "client == null")); } public Builder callFactory(okhttp3.Call.Factory factory) { this.callFactory = checkNotNull(factory, "factory == null"); return this; } public Builder baseUrl(String baseUrl) { checkNotNull(baseUrl, "baseUrl == null"); HttpUrl httpUrl = HttpUrl.parse(baseUrl); if (httpUrl == null) { throw new IllegalArgumentException("Illegal URL: " + baseUrl); } return baseUrl(httpUrl); } public Builder baseUrl(HttpUrl baseUrl) { checkNotNull(baseUrl, "baseUrl == null"); List<String> pathSegments = baseUrl.pathSegments(); if (!"".equals(pathSegments.get(pathSegments.size() - 1))) { throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl); } this.baseUrl = baseUrl; return this; } public Builder addConverterFactory(Converter.Factory factory) { converterFactories.add(checkNotNull(factory, "factory == null")); return this; } public Builder addCallAdapterFactory(CallAdapter.Factory factory) { adapterFactories.add(checkNotNull(factory, "factory == null")); return this; } public Builder callbackExecutor(Executor executor) { this.callbackExecutor = checkNotNull(executor, "executor == null"); return this; } public Builder validateEagerly(boolean validateEagerly) { this.validateEagerly = validateEagerly; return this; } public Retrofit build() { if (baseUrl == null) { throw new IllegalStateException("Base URL required."); } okhttp3.Call.Factory callFactory = this.callFactory; if (callFactory == null) { callFactory = new OkHttpClient(); } Executor callbackExecutor = this.callbackExecutor; if (callbackExecutor == null) { callbackExecutor = platform.defaultCallbackExecutor(); } // Make a defensive copy of the adapters and add the default Call adapter. List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories); adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); // Make a defensive copy of the converters. List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories); return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories, callbackExecutor, validateEagerly); } } }
上面是Retrofit源码,Retrofit和OkHttp的区别就是,构造方法不是public的,不能直接new出来,只能通过Builder.build();返回一个Retrofit实例。
AlertDialog就不举例了,有兴趣的可以自己看下源码。
4.什么是Builder模式
定义:将一个复杂对象的构建与表示相分离,使得同样的构建过程可以创建不同的表示。大白话就是,你不需要知道这个类的内部是什么样的,只用把想使用的参数传进去就可以了,达到了解耦的目的。
UML图:下图是一个GOF的传统的Builder模式的图例。由四部分组成1.Director,2.AbstractBuilder(抽象建造者),3.ConcreteBuilder(具体建造者),4.Product(产品类)。其实这里也可以将Product,分为AbstractProduct(抽象产品类),ConcreteProduct(具体产品类)。
builder.png
使用场景:
相同的方法不同的执行顺寻,产生不同的事件结果。
多个部件或零件都可以装配到一个对象中,但是产生的运行结果又不相同时。
产品类特别复杂,或者产品类中的调用顺序不同产生了不同的作用,这个时候使用Builder设计模式
初始化一个对象特别复杂,参数多,且很多参数都有默认值。
5.简化:
其实仔细观察之后就会发现。无论时Retrofit还是OkHttp为什么都和传统的Builder模式不一样呢,没有Director类,没有抽象建造者类,也没有具体建造者类。那是因为,在使用过程中,这些库的作者,包括AlertDialog的作者,谷歌的开发人员,都将这三个类简化进了一个Builder类。所以说,我们在使用用设计模式的时候不要太过死板。而应随机应变,连Google的开发人员都是这样的,我们当然也可以取其精华去其糟粕。
/** * 作者:jtl * 日期:Created in 2019/1/28 11:19 * 描述:Person类(Builder模式) * 更改: */public class Person { private String name; private int age; private Person(Builder builder) { this.name = builder.name; this.age = builder.age; } public String getName() { return name; } public int getAge() { return age; } public Builder newBuilder() { return new Builder(this); } public static class Builder { private String name; private int age; public Builder() { this.age = 0; this.name = ""; } Builder(Person person) { this.name = person.name; this.age = person.age; } public Builder setAge(int age) { this.age = age; return this; } public Builder setName(String name) { this.name = name; return this; } public Person create() { return new Person(this); } } }
这个是类似与Retrofit和OkHttp的Builder设计模式的一个简化Person类。大家可以仿照这个试一下。
6.区别:
Builder设计模式,之前我们可能没有使用过,但是一些经典的第三方库,却都使用了,将对象的创建与表示相分离,用户不用关心它的内在是什么样的。只需要知道传给他什么参数。在一些有很多参数的产品类中使用该模式,可以避免我们在构造函数中传入大量的默认参数来赋值的尴尬比如:
private String name;//姓名 private int age;//年龄 private String height;//身高 private String weight;//体重 private String sex;//性别 private String address;//家庭住址 private String nation;//种族 private String grade;//年纪 private String clazz;//班级 public Person(String name, int age, String height, String weight, String sex, String address, String nation, String grade, String clazz) { this.name = name; this.age = age; this.height = height; this.weight = weight; this.sex = sex; this.address = address; this.nation = nation; this.grade = grade; this.clazz = clazz; }
而使用了Builder模式后,我们不需要关心Person类内部做了什么样的操作。
/** * 作者:jtl * 日期:Created in 2019/1/28 11:19 * 描述:简化的Builder模式 * 更改: */public class Person { private String name;//姓名 private int age;//年龄 private String height;//身高 private String weight;//体重 private String sex;//性别 private String address;//家庭住址 private String nation;//种族 private String grade;//年纪 private String clazz;//班级// public Person(String name, int age, String height, String weight, String sex, String address, String nation, String grade, String clazz) {// this.name = name;// this.age = age;// this.height = height;// this.weight = weight;// this.sex = sex;// this.address = address;// this.nation = nation;// this.grade = grade;// this.clazz = clazz;// } private Person(Builder builder) { this.name = builder.name; this.age = builder.age; this.height = builder.height; this.weight = builder.weight; this.sex = builder.sex; this.address = builder.address; this.nation = builder.nation; this.grade = builder.grade; this.clazz = builder.clazz; } public String getName() { return name; } public int getAge() { return age; } public Builder newBuilder() { return new Builder(this); } public static class Builder { private String name;//姓名 private int age;//年龄 private String height;//身高 private String weight;//体重 private String sex;//性别 private String address;//家庭住址 private String nation;//种族 private String grade;//年纪 private String clazz;//班级 public Builder() { this.age = 0; this.name = ""; this.height="150cm"; this.weight="45kg"; this.sex="男"; this.address=""; this.nation=""; this.grade="一年级"; this.clazz="一班"; } Builder(Person person) { this.name = person.name; this.age = person.age; this.height = person.height; this.weight = person.weight; this.sex = person.sex; this.address = person.address; this.nation = person.nation; this.grade = person.grade; this.clazz = person.clazz; } public Builder setAge(int age) { this.age = age; return this; } public Builder setName(String name) { this.name = name; return this; } public Builder setHeight(String height) { this.height = height; return this; } public Builder setWeight(String weight) { this.weight = weight; return this; } public Builder setSex(String sex) { this.sex = sex; return this; } public Builder setAddress(String address) { this.address = address; return this; } public Builder setNation(String nation) { this.nation = nation; return this; } public Builder setGrade(String grade) { this.grade = grade; return this; } public Builder setClazz(String clazz) { this.clazz = clazz; return this; } public Person create() { return new Person(this); } } }
可能有的小伙伴们就要问了:为什么不直接给Person类当中的参数,一个set方法呢?这样不就不用Builder模式了嘛。关于这个问题,我是这样理解的,如果我们给Person类每一个参数都设置一个set方法,这样会增加Person类的功能和职责。违反了单一原则,不利于后期的维护。
作者:BurdeNing
链接:https://www.jianshu.com/p/6748c874aef3