问题
最近在做项目时,经常遇到包含上十个属性的实体类,主要出现以下问题
- 创建类的对象时,需要传入许多参数,或使用大量的set方法,代码极其臃肿,可读性差;
- 其他人阅读代码时,难以快速理解其中的属性,增加学习成本;
最终,给我启发的是以下这段规则:
当构造器参数超过四个时,建议使用建造者模式
下面我们来看一下基础的建造者模式是怎样的,并一步步解决上面提到的问题;
引入场景
建造者模式,简而言之,就是一个复杂对象的构造和表示分离,用于应对内部数据、结构复杂的类(通常是持有数据的类,如VO、Config、PO、Entity等);
假设有这样一个场景,你需要建造两类房子-普通房子和别墅,那么与之对应的有两种解决方案:
- 你已知建造方法,自己建造两类房子;
- 你不知道建造方法,将建造的权利交给已知建造方法的人,让他来建造两类房子;
普通解法
目前已知建造方法,自己建造两类房子,将现实场景转化为类图
普通解法的实现
// AbstractHouse.java
public abstract class AbstractHouse{
abstact void buildBasic();
abstact void buildWall();
abstact void buildRoof();
public void build();
}
// CommonHouse.java
public class CommonHouse extends AbstractHouse{
@Override
void buildBasic() {
System.out.println("Common House build Basic");
}
@Override
void buildWall() {
System.out.println("Common House build Wall");
}
@Override
void buildRoof() {
System.out.println("Common House build roof");
}
}
// HighBuilding.java
public class CommonHouse extends AbstractHouse{
@Override
void buildBasic() {
System.out.println("High Building build Basic");
}
@Override
void buildWall() {
System.out.println("High Building build Wall");
}
@Override
void buildRoof() {
System.out.println("High Building build roof");
}
}
以上为房子的抽象类和两个实现类,客户建房子的过程如下
// Client.java
public class Client {
public static void main(String[] args) {
// build common house
CommonHouse commonHouse = new CommonHouse();
commonHouse.build();
// build high building
HighBuilding highBuilding=new HighBuilding();
highBuilding.build();
}
}
分析:可以看到Client中客户需要自己调用build方法去执行建造房子的过程,相当于建房子的过程是暴露给客户端的,违背了开闭原则。
建造者模式
建造者将复杂对象的构造过程抽象出来,使得这个对象可以通过不同的构造过程构造出不同属性的对象,它使得用户只需知道复杂对象的内容和类型就可以构造它们,而无须知晓内部的构造细节,建造者有四个角色:
- Product:角色;
- Builder:抽象构造者,创建一个产品对象的各个部件指定的接口/抽象;
- ConcreteBuilder:Builder的实现,实现接口,构建和装配各个部件;
- Director:指挥者,构建一个使用Builder接口的对象,主要是用于创建一个复杂对象,有两个作用:
- 隔离客户和生产对象的过程;
- 负责控制生产产品的过程;
类图如下:
在建房子的场景中,具体类图如下:
实现代码如下:
// House.java(相当于Product)
public class House {
private String basic;
private String wall;
private String roof;
// 省略set、get方法
}
// HouseBuilder.java(相当于Builder)
public abstract class HouseBuilder {
private House house=new House();
abstract void buildBasic();
abstract void buildWall();
abstract void buildRoof();
public House build(){
return house;
}
}
// CommonHouse.java(相当于ConcreteBuilder)
public class CommonHouse extends HouseBuilder{
@Override
void buildBasic() {
System.out.println("Common house build basic");
}
@Override
void buildWall() {
System.out.println("Common house build wall");
}
@Override
void buildRoof() {
System.out.println("Common house build roof");
}
}
// HighBuilder.java(相当于ConcreteBuilder)
public class CommonHouse extends HouseBuilder{
@Override
void buildBasic() {
System.out.println("High Building build basic");
}
@Override
void buildWall() {
System.out.println("High Building build wall");
}
@Override
void buildRoof() {
System.out.println("High Building build roof");
}
}
// HouseDirector.java(相当于Director)
public class HouseDirector {
private HouseBuilder houseBuilder = null;
// 构造器传入HouseBuilder
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
// 指挥具体建造流程
public House construct(){
houseBuilder.buildBasic();
houseBuilder.buildWall();
houseBuilder.buildRoof();
return houseBuilder.build();
}
}
客户端调用如下
// Client.java
public class Client {
public static void main(String[] args) {
// 建造CommonHouse
CommonHouse commonHouse = new CommonHouse();
HouseDirector houseDirector = new HouseDirector(commonHouse);
houseDirector.construct();
// 建造HighBuilding
HighBuilding highBuilding = new HighBuilding();
HouseDirector houseDirector2 = new HouseDirector(highBuilding);
houseDirector2.construct();
}
}
分析:可以看到,通过director这一角色,将建房子的过程与客户端隔离开来,使得用户只需关心需要建造的房子的类型,而无须关注建房子的过程;
简化实现
现在我们回到文章开头的准则:构造器参数超过四个,使用建造者模式,问题是,如何使用?下面给出一段示例代码
// NewComputer.java
public class NewComputer {
private String cpu;
private String screen;
private String memory;
private String mainboard;
// 禁止调用构造器
public NewComputer() {
throw new RuntimeException("cannot init!");
}
private NewComputer(Builder builder) {
cpu = builder.cpu;
screen = builder.screen;
memory = builder.memory;
mainboard = builder.mainboard;
}
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Builder() {
}
public Builder cpu(String val) {
this.cpu = val;
return this;
}
public Builder screen(String val) {
this.screen = val;
return this;
}
public Builder memory(String val) {
this.memory = val;
return this;
}
public Builder mainboard(String val) {
this.mainboard = val;
return this;
}
public NewComputer build(){
return new NewComputer(this);
}
}
}
// Client.java
public class Client {
public static void main(String[] args) {
// 建造者模式
NewComputer newComputer = new NewComputer.Builder()
.cpu("new cpu")
.screen("new screen")
.memory("new memory")
.mainboard("new mainboard")
.build();
}
}
上述代码相当于对建造者模式的简化,NewComputer(产品)内部定义了一个静态内部类Builder(ConcreteBuilder),即省去了建造者模式中的Director、Builder,使得产品与创建过程之间的联系更加紧密,简化了建造者模式,同时客户端仍然只能通过产品内部的Builder生产产品,也达到了隔离用户与产品生产过程的目的;
Lombok的@Builder注解
lombok中可以使用@Builder实现构造者模式,示例如下
// NewComputer.java
@Builder
public class NewComputer {
private String cpu;
private String screen;
private String memory;
private String mainboard;
}
编译以后的.class文件如下
// NewComputer.class
public class NewComputer {
private String cpu;
private String screen;
private String memory;
private String mainboard;
NewComputer(final String cpu, final String screen, final String memory, final String mainboard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.mainboard = mainboard;
}
public static NewComputer.NewComputerBuilder builder() {
return new NewComputer.NewComputerBuilder();
}
public static class NewComputerBuilder {
private String cpu;
private String screen;
private String memory;
private String mainboard;
NewComputerBuilder() {
}
public NewComputer.NewComputerBuilder cpu(final String cpu) {
this.cpu = cpu;
return this;
}
public NewComputer.NewComputerBuilder screen(final String screen) {
this.screen = screen;
return this;
}
public NewComputer.NewComputerBuilder memory(final String memory) {
this.memory = memory;
return this;
}
public NewComputer.NewComputerBuilder mainboard(final String mainboard) {
this.mainboard = mainboard;
return this;
}
public NewComputer build() {
return new NewComputer(this.cpu, this.screen, this.memory, this.mainboard);
}
public String toString() {
return "NewComputer.NewComputerBuilder(cpu=" + this.cpu + ", screen=" + this.screen + ", memory=" + this.memory + ", mainboard=" + this.mainboard + ")";
}
}
}
可以看到,NewComputer提供了一个静态的公共方法builder,用于外部构造NewComputer.NewComputerBuilder,以下为测试代码:(读者可以自己加一个@ToString注解,便于测试)
NewComputer newComputer = NewComputer.builder() // 注意,这里不是new NewComputer.NewComputerBuilder()!!!
.cpu("cpu")
.memory("memory")
.mainboard("mainboard")
.build();
System.out.println(newComputer.toString());
// 结果:NewComputer(cpu=cpu, screen=null, memory=memory, mainboard=mainboard)
优缺点及适用场景
- 优点:
- 隔离用户与产品生产过程,符合开闭原则;
- 使用静态内部类简化建造者模式,便于代码的书写;
- 不需要了解复杂对象的内容,降低了学习成本;
- 具体建造者之间是相互独立的,有利于系统的扩展;
- 缺点:
- 每次使用建造者模式都会多创建类,降低性能;
- 若产品内部极其复杂,则可能创建很多具体建造者类来实现,系统会变得庞大;
- 适用场景:
- 对性能要求不高
- 需要创建的产品有较多的共同点
- 需要创建的类字段超过四个,建议使用建造者模式
以上就是对建造者模式的简要介绍,这里是10一世界,如果喜欢这篇文章,请点个赞吧!!!