我们构造一个类常用的有两种方式
一种方式是采用多重载的构造函数,把不同搭配的属性写成不同的构造函数,这种方式的缺点在于代码的重复工作量会非常大,而且用户在用的时候很难分清楚每一种构造函数是干什么的,而且参数顺序输不对可是个大问题,使用起来十分不方便。
另一种方法是通过get,set的方法,这种方法比较容易理解,但是缺点也很明显,一个是参数变量不能是final类型,而且用户不知道什么时候他才能得到一个完整的变量(比方说用户还有很重要的属性没有set就去使用这个变量了)
针对以上的缺点,我们采用builder模式去构建一个新的对象
传统builder模式
传统建造者模式包含四个角色
- builder(抽象构造者):这个是一个抽象类或者接口,用于抽象具体建造者的函数。
- ConcreteBuilder(具体建造者):这是抽象构造者的具体实现,实现构造逻辑
- Director (指导者):可以叫指导者,也可以叫服务员,他不需要知道builder是如何建造产品的,用户直接和指导者进行沟通,指导者有两种操作:告诉builder去建造一个商品;把商品交付给用户
- Product (产品类):这是我们具体要构造的的对象。
代码
产品类
public class Car
{
private String wheel;
private String skeleton;
private String engine;
// 省略getter和setter方法
}
抽象建造者类
public interface ICarBuilder
{
public void buildWheel();
public void buildSkeleton();
public void buildEngine();
Car buildCar();
}
具体建造者类
public class ConcreteBuilder implements ICarBuilder
{
Car car;
public ConcreteBuilder()
{
car = new Car();
}
@Override
public ConcreteBuilder buildWheel()
{
car.setWheel("轮子");
}
@Override
public ConcreteBuilder buildSkeleton()
{
car.setSkeleton("车身结构");
}
@Override
public ConcreteBuilder buildEngine()
{
car.setEngine("发动机");
}
//前面那些builder方法返回的都是builder,只有当调用下面的build方法的时候才最终构建出了该对象
@Override
public Car buildCar()
{
return this.car;
}
}
public class CarDirector
{
public Car constructCar(ICarBuilder builder)
{
builder.buildEngine();
builder.buildSkeleton();
builder.buildWheel();
return builder.buildCar();
}
}
public class MainTest
{
public static void main(String[] args)
{
CarDirector director = new CarDirector();//在使用的时候先声明中间的服务员,也就是指导者
Car car = director.constructCar(new ConcreteBuilder());//通过指导者把车构建出来
System.out.println(car.getWheel());
System.out.println(car.getEngine());
System.out.println(car.getSkeleton());
}
}
此段代码作者朱小厮 原文:https://blog.csdn.net/u013256816/article/details/50978024?utm_source=copy
主要优点是
- 在建造过程中对象是未生成的,最后调用build方法之后才会生成该对象
- 有一个builder的interface,扩展性更强,并且可以把build过程与交付给用户的过程解耦开,使用更灵活,封装性好
- 而且这也形成了漂亮的链式调用
Person person = new Person.PersonBuilder("cj")
.age(24)
.job("java")
.location("苏州")
.builder();
变种builder模式
在上面的builder模式中,优点显而易见,可扩展性强,但是造成的结果是代码冗余量大,凭空多了三个类,而且并不是所有的情况都需要多个不同实现的builder的,变种builder模式的主要目的是减少多余类的创建,省略了builder抽象类和指导者。
代码
这里主要结合OKHttp源码讲解
public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final RequestBody body;
final Object tag;
private volatile CacheControl cacheControl; // Lazily initialized.
Request(Builder builder) {
//构造函数,省略属性赋值操作
}
public Builder newBuilder() {//这里读者可以思考一下为什么会有一个newbuilder模式呢
return new Builder(this);
}
//省略部分代码
public static class Builder {
HttpUrl url;
String method;
Headers.Builder headers;
RequestBody body;
Object tag;
//省略部分代码
Builder(Request request) {//这里读者可以想一想为什么在builder中会有这样一个构造方法,既然我们在创造builder的时候request是空的,那我们什么时候会用到这个方法呢?
//构造函数,省略属性赋值操作
}
//省略部分代码
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
}
在变种builder模式中,直接将指挥者和抽象builder类去掉了,取而代之的是静态内部类builder,牺牲了一部分的可扩展性,将代码逻辑大大简化。
这种builder的作用和传统builder是一样的
这个时候有一个问题,当我的对象属性相当多,而且我现在要新建一个对象,这个对象的属性值只有一两个和我已有的对象不同,这个时候我要怎么做?
粘贴上一段代码?重新输入?
毕竟builder模式创建的对象一但创建成功里面的属性就不能再修改,所以拷贝一个对象再更改属性的方法是不可行的
我在一开始读OKhttp的源码的时候也很好奇它里面为什么会有一个newBuilder方法,builder中会有一个参数为request的构造方法,这里不得不惊叹设计者的强大,原文在此
public Builder newBuilder() {
return new Builder(this);
}
这里通过原有的request的newBuilder方法生成一个新的,并且属性值和request一样的builder,之后再用这个新的builder更改属性后生成新的request。
巧妙地通过产品构造出builder,更改builder产生全新的产品。通过逆向构造的过程完成了整个拷贝
对比传统builder模式和变种builder模式
传统的builder模式优点是可扩展性强,但是同时缺点是要创造4个类,对于初学者很不友好,而且代码冗余量大,变种builder使用更加便捷,操作更加方便