建造者模式:
建造模式可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。当构造方法参数过多时使用建造者模式。
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示,通俗的说就是:建造者模式就是如何一步步构建一个包含多个组成部件的对象,相同的构建过程可以创建不同的产品。
1. 简单的构造模式
UML类图:
具体代码实现:
被建造的对象-----Computer.java
/**
* 产品类--被建造的对象
*/
public class Computer {
private String cpu ; // cpu
private String hardDisk ; //硬盘
private String mainBoard ; // 主板
private String memory ; // 内存
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getHardDisk() {
return hardDisk;
}
public void setHardDisk(String hardDisk) {
this.hardDisk = hardDisk;
}
public String getMainBoard() {
return mainBoard;
}
public void setMainBoard(String mainBoard) {
this.mainBoard = mainBoard;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
}
抽象的建造者-----Builder.java
/**
* 抽象的建造者,即装电脑的步骤
* 至于安装什么型号的主板,不是我关心,而是具体的建造者关心的
*/
public interface Builder {
// 安装主板
void createMainBoard(String mainBoard) ;
// 安装 cpu
void createCpu(String cpu) ;
// 安装硬盘
void createhardDisk(String hardDisk) ;
// 安装内存
void createMemory(String memory) ;
// 组成电脑
Computer createComputer() ;
}
具体建造者 -----AssemblerBuilder.java
public class AssemblerBuilder implements Builder {
Computer computer = new Computer();
@Override
public void createMainBoard(String mainBoard) {
computer.setMainBoard(mainBoard);
}
@Override
public void createCpu(String cpu) {
computer.setCpu(cpu);
}
@Override
public void createhardDisk(String hardDisk) {
computer.setHardDisk(hardDisk);
}
@Override
public void createMemory(String memory) {
computer.setMemory(memory);
}
@Override
public Computer createComputer() {
return computer;
}
}
/**
* 声明一个导演类「指挥者,这里可以装电脑的老板」,用来指挥组装过程,也就是组装电脑的流程
*/
public class Director {
private Builder builder;
// 使用多态,装机工很多,老板可以指挥各个装几个
public Director(Builder builder){
this.builder = builder;
}
// 老板最后只想获得装成的成品
public Computer createComputer(String cpu,String hardDisk,String mainBoard,String memory){
// 具体工作是装机工去做
this.builder.createMainBoard(mainBoard);
this.builder.createCpu(cpu);
this.builder.createMemory(memory);
this.builder.createhardDisk(hardDisk);
return this.builder.createComputer();
}
}
复制代码
测试:
public class Test {
public static void main(String[] args) {
// 装机员小美
Builder builder = new AssemblerBuilder();
// 老板把小明的需求转给小美
Director director = new Director(builder);
// 老板最后拿到成品电脑,工作小美去做
Computer computer = director.createComputer("Intel 酷睿i9 7900X","三星M9T 2TB (HN-M201RAD)","技嘉AORUS Z270X-Gaming 7","科赋Cras II 红灯 16GB DDR4 3000");
System.out.println("小明使用的电脑是:\n"+computer.getMainBoard()+"主板\n"+computer.getCpu()+"CPU\n"+computer.getHardDisk()+"硬盘\n"+computer.getMemory()+"内存\n");
}
}
建造者模式的优缺点
- 优点
- 使创建产品的步骤「把创建产品步骤放在不同的方法中(AssemblerBuilder.java中的各个方法),更加清晰直观」和产品本身分离,即使用相同的创建过程创建出不同的产品。
- 每个建造者都是独立的互不影响,这样就达到解耦的目的,所以如果想要替换现有的建造者那非常方便,添加一个实现即可。
- 缺点
- 只适用于产品具有相同的特点「过程和步骤」,如果产品之间差异非常大,则不适用「使用范围受限」
- 万一那天产品内部发生改变,那多个建造者都要修改,成本太大
建造者模式的使用场景
- 如果一个对象有非常复杂的内部结构「这些产品通常有很多属性」,那么使用建造者模式
- 如果想把复杂对象的创建和使用分离开来,那么使用建造者模式「使用相同的创建步骤可以创建不同的产品」
建造者模式的另一种写法:
当我看到建造者模式时,第一个想到的就是lombok的@builder的注解。
由于是用Builder模式来创建某个对象,因此就没有必要再定义一个Builder接口,直接提供一个具体的建造者类就可以了。
对于创建一个复杂的对象,可能会有很多种不同的选择和步骤,干脆去掉“导演者”,把导演者的功能和Client的功能合并起来,也就是说,Client这个时候就相当于导演者,它来指导构建器类去构建需要的复杂对象。
先看下面这个例子该如何处理:
public class User{
String name;
int age;
String email;
String address;
public User(){
}
//想要有名字和邮箱的构造器
public User(String name, String email){
this.name = name;
this.email = email;
}
//想要有名字和地址的构造器
public User(String name, String address){
this.name = name;
this.address = address;
}
}
在上面代码中,很容易可以发现,在我们正常的需求下,Java构造器的编写将会出问题,由于参数个数和类型一样无法构成重载,所以这样写是不行的,那么我们可以通过Builder模式解决这种情况。
public class User {
String name;
int age;
String phone;
String email;
String address;
//注意无参构造器私有,避免外界使用构造器创建User对象
private User() {
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + ", phone=" + phone + ",
email=" + email + ", address=" + address
+ "]";
}
public static class Builder {
private String name;
private int age;
private String phone;
private String email;
private String address;
public Builder() {
}
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setAge(int age) {
this.age = age;
return this;
}
public Builder setPhone(String phone) {
this.phone = phone;
return this;
}
public Builder setEmail(String email) {
this.email = email;
return this;
}
public Builder setAddress(String address) {
this.address = address;
return this;
}
public User build() {
User user = new User();
user.name = name;
user.age = age;
user.email = email;
user.phone = phone;
user.address = address;
return user;
}
}
}
根据上面的代码,我们可以看出来,就是在User内部创建一个内部类,并且拥有和User一样的字段(属性),并且提供SET方法,最重要的是要提供一个能够返回User对象的方法(build),这样才能通过Builder来创建User对象。
Builder设计模式还有一个好处,那便是我们可以随意组合输入的参数,不仅避免了重载出错的问题,还不需要写过多的构造器。
下面我们一起看看写完Builder模式类之后如何来调用:
public class UserTest {
public static void main(String[] args) {
User u = new User.Builder().setName("bob").setAge(22).build();
System.out.println(u);
}
}
看到这个写法是不是很熟悉,和我们使用lombok中得@builder 注解构造对象的方式是一样的。我们看一下lombok中@builder反编译后的代码
@Builder
public class User {
private final Integer code = 200;
private String username;
private String password;
}
// 编译后:
public class User {
private String username;
private String password;
User(String username, String password) {
this.username = username; this.password = password;
}
public static User.UserBuilder builder() {
return new User.UserBuilder();
}
public static class UserBuilder {
private String username;
private String password;
UserBuilder() {}
public User.UserBuilder username(String username) {
this.username = username;
return this;
}
public User.UserBuilder password(String password) {
this.password = password;
return this;
}
public User build() {
return new User(this.username, this.password);
}
public String toString() {
return "User.UserBuilder(username=" + this.username + ", password=" + this.password + ")";
}
}
}
使用builder模式的好处:
1. 通过构造函数传参的方式来进行构建(臃肿 不能灵活地只设置某些参数)
2.写很多构造方法。而且容易吧两个属性的参数写错位置等等;(重载很多的构造器,然后可以保证数据的一致性,比较安全)
3.写一堆setter方法,缺点就是够早的过程会被分到几个调用中。构造可以出现不一致的状态。(而且代码很啰嗦)
4.采用builder模式的话,当一个类的参数很多的时候。
(使用javabean模式,调用一个无参的构造器,然后调用setter方法来设置每个必要的参数。但是javabean自身有着严重的缺点,因为构造过程被分到几个调用中,在构造javabean可能处于不一致的状态,类无法仅仅通过检验构造器参数的有效性来保证一致性。也就是set的过程中其实很多时候并不能保证一致性,因为set已经算是分开执行了)
build模式 既能保证像重叠构造器那样的安全,也能实现JavaBean模式那样的可读性。 (不用操心线程)