前言
建造者模式(Builder Pattern)又称为创建者模式,建造者模式使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在该模式中存在一个Builder类,这个类是独立于其他对象的,在建造者模式中使用Builder类一步一步的构造最终的对象。
记得在刚开始接触web开发的时候,后端在接收到前端请求的参数时,后端需要不断的使用set函数来构造一个对象,比如现在需要构造一个Student类,但是我就写了这么一串又臭又长的代码。
Student student = new Student();
student.setId("id");
student.setName("1999single");
student.setGrade("17");
student.setMajor("软件工程");
... ...
是吧,看着头疼,写着也头疼。直到接触到了建造者模式,心中的太阳升起来了……
介绍
意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
何时使用:一些基本部件不会变,而其组合经常变化的时候。
如何解决:将变与不变分离开。
关键代码:产品:创建的实力,建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。
应用实例: 1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。 2、JAVA 中的
StringBuilder。优点: 1、建造者独立,易扩展。 2、便于控制细节风险。
缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。
使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。
注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
结构
产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个滅部件。
抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法。
具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
实现
既然称作建造者模式,这里就建造一座房子好勒!假设一种房子有四种属性:几楼、什么样的门、什么样的窗户、外墙是什么颜色的。
/**
* Product(产品角色)
*/
public class House {
private String storey;
private String door;
private String window;
private String color;
public String getStorey() {
return storey;
}
public void setStorey(String storey) {
this.storey = storey;
}
public String getDoor() {
return door;
}
public void setDoor(String door) {
this.door = door;
}
public String getWindow() {
return window;
}
public void setWindow(String window) {
this.window = window;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "House{" +
"storey='" + storey + '\'' +
", door='" + door + '\'' +
", window='" + window + '\'' +
", color='" + color + '\'' +
'}';
}
}
还需要有一个建筑队,他们有着建造房子的技术。
/**
* Builder(抽象建造者)
*/
public interface HouseBuilder {
void buildStorey();
void buildDoor();
void buildWindow();
void buildColor();
House build();
}
public class ChinaHouseBuilder implements HouseBuilder {
private House house;
public ChinaHouseBuilder() {
this.house = new House();
}
@Override
public void buildStorey() {
house.setStorey("5");
}
@Override
public void buildDoor() {
house.setDoor("steel door");
}
@Override
public void buildWindow() {
house.setWindow("steel window");
}
@Override
public void buildColor() {
house.setColor("white");
}
@Override
public House build() {
return house;
}
}
此时还需要一个指挥的人,选取善于修建中式的房屋的建筑队还是善于修建复式房屋的建筑队,以及告诉建筑队该房屋需要建哪一些东西,比如我就觉得房子的外装没必要,不如把外装的钱省下来用在内装!!内在美才是真的美!!
public class Director {
public House bulidHouse(HouseBuilder builder) {
builder.buildStorey();
builder.buildDoor();
builder.buildWindow();
builder.buildColor();
return builder.build();
}
}
最后当然就是老板出钱盖房子咯(调用)
public class Main {
public static void main(String[] args) {
Director director = new Director();
House house = director.bulidHouse(new ChinaHouseBuilder());
System.out.println(house);
}
}
到这里一个简单的建造者模式就构建完成了,我们发现每一个Builder子类的都非常的独特(写得死),假如我要修建一个6楼的房子我又需要新建一个Builder子类,假如我不要窗户又得新建一个Builder子类,这也是建造者模式的缺点。
为了解决这个缺点,又产生了建造者模式的链式方法,同时也提高了代码编写的舒适度。
链式调用
有些朋友可能不熟悉什么是链式调用,咱们拿两个在实际项目中的例子来说明,一样就是看出所谓链式是什么意思。
// 安卓的一个加载图片的库,基于建造者模式和链式调用的思想诞生的。
Glide.with(context) // context为安卓开发中的上下文
.load(internetUrl) // internetUrl网络图片的地址
.into(targetImageView); // targetImageView为现实图片的一个组件/容器
// okhttp中创建request的方法
Request request = new Request.Builder()
.url(url) // 请求的url
.get() // 设置请求的方式为GET,相应的还有.post()方法
.build(); // 建造对象的最后一步
可以看出他们共同的特点就是不断的 .().().().().().() 写代码的舒适性立马提高,接下来我们来讨论下如何实现这样的链式结构
链式调用实现
我们还是使用上面的造房子的例子来说明
// product
public class House {
private String storey;
private String door;
private String window;
private String color;
public House(String storey, String door, String window, String color) {
this.storey = storey;
this.door = door;
this.window = window;
this.color = color;
}
public House() {
}
public String getStorey() {
return storey;
}
public void setStorey(String storey) {
this.storey = storey;
}
public String getDoor() {
return door;
}
public void setDoor(String door) {
this.door = door;
}
public String getWindow() {
return window;
}
public void setWindow(String window) {
this.window = window;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "House{" +
"storey='" + storey + '\'' +
", door='" + door + '\'' +
", window='" + window + '\'' +
", color='" + color + '\'' +
'}';
}
// product中静态的方法生成一个Builder
public static HouseBuilder builder() {
return new HouseBuilder();
}
// Builder
public static class HouseBuilder {
private String storey;
private String door;
private String window;
private String color;
public String getStorey() {
return storey;
}
// 返回自身对象,用于下一步的调用
public HouseBuilder storey(String storey) {
this.storey = storey;
return this;
}
public String getDoor() {
return door;
}
public HouseBuilder door(String door) {
this.door = door;
return this;
}
public String getWindow() {
return window;
}
public HouseBuilder window(String window) {
this.window = window;
return this;
}
public String getColor() {
return color;
}
public HouseBuilder color(String color) {
this.color = color;
return this;
}
// 最后构建House
public House bulid() {
return new House(storey, door, window, color);
}
}
}
调用:
House house = House.builder().storey("2").window("not").door("kk").color("blue").build();
Lombok中的建造者模式(链式调用)
使用Lombok可以非常快速的实现类的建造者模式,我们还是对House进行操作
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
public class House {
private String storey;
private String door;
private String window;
private String color;
}
好的,写完了,晚安!使用Lombok就是这么方便,什么Builder什么Director都不要了。
再来看一下调用,也非常的简单。
House hh = House.builder()
.storey("3")
.color("black")
.window("wooden")
.door("wooden")
.bulid();
Lombok实现建造者原理(简单说说)
首先我们反编译一下编译器生成的House.class得到
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package builderPattern;
public class House {
private String storey;
private String door;
private String window;
private String color;
public static House.HouseBuilder builder() {
return new House.HouseBuilder();
}
public House() {
}
public House(String storey, String door, String window, String color) {
this.storey = storey;
this.door = door;
this.window = window;
this.color = color;
}
public String getStorey() {
return this.storey;
}
public String getDoor() {
return this.door;
}
public String getWindow() {
return this.window;
}
public String getColor() {
return this.color;
}
public void setStorey(String storey) {
this.storey = storey;
}
public void setDoor(String door) {
this.door = door;
}
public void setWindow(String window) {
this.window = window;
}
public void setColor(String color) {
this.color = color;
}
public static class HouseBuilder {
private String storey;
private String door;
private String window;
private String color;
HouseBuilder() {
}
public House.HouseBuilder storey(String storey) {
this.storey = storey;
return this;
}
public House.HouseBuilder door(String door) {
this.door = door;
return this;
}
public House.HouseBuilder window(String window) {
this.window = window;
return this;
}
public House.HouseBuilder color(String color) {
this.color = color;
return this;
}
public House build() {
return new House(this.storey, this.door, this.window, this.color);
}
}
}
为了便于阅读代码我删掉了一些于建造者模式无关的代码(使用注解后Lombok还会自动生成toString方法和hashCode方法),其实Lombok只是自动帮我们生成了代码而已,实际上还是这么一个思路:–>创建Bulider–>开始建造–>最后bulid。
至于Lombok是怎么实现动态生成代码,这就要追溯他的原理的,首先他的注解是用于source的,java编译成class后,注解就不存在于字节码文件中了,所以Lombok其实是在代码编译时期动态的生成代码插入到编译树中的。(关于编译原理不说太多了,这学期刚挂科。有时间再自己实现一下Lombok的@ToString注解,有兴趣的朋友可以关注一下我!挂科博主耻辱下线,晚安。)