Builder模式
一、场景
建造者模式是日常开发中比较常见的设计模式,它的主要作用就是将复杂事物创建的过程抽象出来,该抽象的不同实现方式不同,创建出的对象也不同。通俗的讲,创建一个对象一般都会有一个固定的步骤,这个固定的步骤我们把它抽象出来,每个抽象步骤都会有不同的实现方式,不同的实现方式创建出的对象也将不同。举个常见的例子,想必大家都买过电脑,电脑的生产或者组装其实就是属于建造者模式,我们知道,电脑的生产都需要安装CPU、内存条、硬盘等元器件。
当一个类的构造函数参数超过4个,而且这些参数有些是可选的时,我们通常有两种办法来构建它的对象。 例如我们现在有如下一个类计算机类Computer,其中cpu与ram是必填参数,而其他3个是可选参数,那么我们如何构造这个类的实例呢,通常有两种常用的方式:
public class Computer { private String cpu;//必须 private String ram;//必须 private int usbCount;//可选 private String keyboard;//可选 private String display;//可选 }
第一:折叠构造函数模式(telescoping constructor pattern )--多个重载的构造函数
这个我们经常用,如下代码所示
public class Computer { ... public Computer(String cpu, String ram) { this(cpu, ram, 0); } public Computer(String cpu, String ram, int usbCount) { this(cpu, ram, usbCount, "罗技键盘"); } public Computer(String cpu, String ram, int usbCount, String keyboard) { this(cpu, ram, usbCount, keyboard, "三星显示器"); } public Computer(String cpu, String ram, int usbCount, String keyboard, String display) { this.cpu = cpu; this.ram = ram; this.usbCount = usbCount; this.keyboard = keyboard; this.display = display; } }
第二种:Javabean 模式--使用 setter 方法挨个构造
如下所示
public class Computer { ... public String getCpu() { return cpu; } public void setCpu(String cpu) { this.cpu = cpu; } public String getRam() { return ram; } public void setRam(String ram) { this.ram = ram; } public int getUsbCount() { return usbCount; } //... }
那么这两种方式有什么弊端呢?
第一种主要是使用及阅读不方便。你可以想象一下,当你要调用一个类的构造函数时,你首先要决定使用哪一个,然后里面又是一堆参数,如果这些参数的类型很多又都一样,你还要搞清楚这些参数的含义,很容易就传混了,维护困难。
第二种方式:好处是:易于阅读,并且可以只对有用的成员变量赋值。缺点是:成员变量不可以是final类型,失去了不可变对象的很多好处。对象状态不连续,因为那个类中的属性是分步设置的。
为了解决这两个痛点,就看builder模式。
二、什么是建造者模式
Builder模式也叫建造者模式或者生成器模式,是由GoF提出的23种设计模式中的一种。
Builder模式是一种对象创建型模式之一,用来隐藏复合对象的创建过程,它把复合对象的创建过程加以抽象,通过子类继承和重载的方式,动态地创建具有复合属性的对象。
建造者模式分为两种,一种为经典建造者模式,另一种为变种建造者模式。
2.1、变种建造者模式--链式方法
可以很好的解决第一种参数过多的问题和第二种的对final成员变量的构造的问题。
基本步骤:
- 在Computer 中创建一个静态内部类 Builder,然后将Computer 中的参数都复制到Builder类中。
- 在Computer中创建一个private的构造函数,参数为Builder类型
- 在Builder中创建一个public的构造函数,参数为Computer中必填的那些参数,cpu 和ram。
- 在Builder中创建设置函数,对Computer中那些可选参数进行赋值,返回值为Builder类型的实例
- 在Builder中创建一个build()方法,在其中构建Computer的实例并返回
public class Computer { private final String cpu;//必须,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在 private final String ram;//必须,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在 private final int usbCount;//可选 private final String keyboard;//可选 private final String display;//可选 private Computer(Builder builder){ this.cpu=builder.cpu; this.ram=builder.ram; this.usbCount=builder.usbCount; this.keyboard=builder.keyboard; this.display=builder.display; } public static class Builder{ private String cpu;//必须 private String ram;//必须 private int usbCount;//可选 private String keyboard;//可选 private String display;//可选 public Builder(String cup,String ram){ this.cpu=cup; this.ram=ram; } public Builder setUsbCount(int usbCount) { this.usbCount = usbCount; return this; } public Builder setKeyboard(String keyboard) { this.keyboard = keyboard; return this; } public Builder setDisplay(String display) { this.display = display; return this; } public Computer build(){ return new Computer(this); } } //省略getter方法 }
如何使用
在客户端使用链式调用,一步一步的把对象构建出来。
Computer computer=new Computer.Builder("因特尔","三星")//必填字段 .setDisplay("三星24寸") .setKeyboard("罗技") .setUsbCount(2) .build();
变种 Builder 模式目的在于减少对象创建过程中引入的多个构造函数、可选参数以及多个 setter 过度使用导致的不必要的复杂性。
为了对比,下面继续看一看经典的建造者模式。
2.2、经典建造者模式的结构
如上图所示,builder模式有4个角色。
- Product: 最终要生成的对象,例如 Computer实例。
- Builder: 构建者的抽象基类(有时会使用接口代替)。其定义了构建Product的抽象步骤,其实体类需要实现这些步骤。其会包含一个用来返回最终产品的方法Product getProduct()。
- ConcreteBuilder: Builder的实现类。
- Director: 决定如何构建最终产品的算法. 其会包含一个负责组装的方法void Construct(Builder builder), 在这个方法中通过调用builder的方法,就可以设置builder,等设置完成后,就可以通过builder的 getProduct() 方法获得最终的产品。
2.2.1、实例
我们接下来将最开始的例子使用传统方式来实现一遍
第一步:我们的目标Computer类:
public class Computer { private String cpu;//必须 private String ram;//必须 private int usbCount;//可选 private String keyboard;//可选 private String display;//可选 public Computer(String cpu, String ram) { this.cpu = cpu; this.ram = ram; } public void setUsbCount(int usbCount) { this.usbCount = usbCount; } public void setKeyboard(String keyboard) { this.keyboard = keyboard; } public void setDisplay(String display) { this.display = display; } @Override public String toString() { return "Computer{" + "cpu='" + cpu + '\'' + ", ram='" + ram + '\'' + ", usbCount=" + usbCount + ", keyboard='" + keyboard + '\'' + ", display='" + display + '\'' + '}'; } }
第二步:抽象构建者类
public abstract class ComputerBuilder { public abstract void setUsbCount(); public abstract void setKeyboard(); public abstract void setDisplay(); public abstract Computer getComputer(); }
第三步:实体构建者类,我们可以根据要构建的产品种类产生多了实体构建者类,这里我们需要构建两种品牌的电脑,苹果电脑和联想电脑,所以我们生成了两个实体构建者类。
苹果电脑构建者类
public class MacComputerBuilder extends ComputerBuilder { private Computer computer; public MacComputerBuilder(String cpu, String ram) { computer = new Computer(cpu, ram); } @Override public void setUsbCount() { computer.setUsbCount(2); } @Override public void setKeyboard() { computer.setKeyboard("苹果键盘"); } @Override public void setDisplay() { computer.setDisplay("苹果显示器"); } @Override public Computer getComputer() { return computer; } }
联想电脑构建者类
public class LenovoComputerBuilder extends ComputerBuilder { private Computer computer; public LenovoComputerBuilder(String cpu, String ram) { computer=new Computer(cpu,ram); } @Override public void setUsbCount() { computer.setUsbCount(4); } @Override public void setKeyboard() { computer.setKeyboard("联想键盘"); } @Override public void setDisplay() { computer.setDisplay("联想显示器"); } @Override public Computer getComputer() { return computer; } }
第四步:指导者类(Director)
public class ComputerDirector { public void makeComputer(ComputerBuilder builder){ builder.setUsbCount(); builder.setDisplay(); builder.setKeyboard(); } }
使用
首先生成一个director (1),然后生成一个目标builder (2),接着使用director组装builder (3),组装完毕后使用builder创建产品实例 (4)。
public static void main(String[] args) { ComputerDirector director=new ComputerDirector();//1 ComputerBuilder builder=new MacComputerBuilder("I5处理器","三星125");//2 director.makeComputer(builder);//3 Computer macComputer=builder.getComputer();//4 System.out.println("mac computer:"+macComputer.toString()); ComputerBuilder lenovoBuilder=new LenovoComputerBuilder("I7处理器","海力士222"); director.makeComputer(lenovoBuilder); Computer lenovoComputer=lenovoBuilder.getComputer(); System.out.println("lenovo computer:"+lenovoComputer.toString()); }
输出结果如下:
mac computer:Computer{cpu='I5处理器', ram='三星125', usbCount=2, keyboard='苹果键盘', display='苹果显示器'}
lenovo computer:Computer{cpu='I7处理器', ram='海力士222', usbCount=4, keyboard='联想键盘', display='联想显示器'}
可以看到,文章最开始的使用方式是传统builder模式的变种, 首先其省略了director 这个角色,将构建算法交给了client端,其次将builder 写到了要构建的产品类里面,最后采用了链式调用。
总结使用场景:
- 对象的创建:Builder模式是为对象的创建而设计的模式,而且构造函数很多,很难决定再开发期间调用哪一个构造函数
- 创建的是一个复合对象:被创建的对象为一个具有复合属性的复合对象;
- 关注对象创建的各部分的创建过程:不同的工厂(这里指builder生成器)对产品属性有不同的创建方法;
参考:https://zhuanlan.zhihu.com/p/58093669