文章目录
建造者模式
建造者模式将复杂对象构建与其表示分离。通过定义抽象建造者和具体建造者,以及指挥者协调构建过程,可精细控制对象创建。适用于创建复杂对象,提高代码可读性和可维护性,方便创建不同配置的对象。
建造者模式与工厂模式有什么区别
一、目的不同
- 建造者模式:侧重于复杂对象的逐步构建,通过多个步骤来组装一个复杂对象,允许对象在构建过程中有不同的表示形式,更注重对象创建的过程控制。
- 工厂模式:主要目的是根据不同的条件创建不同类型的对象,重点在于对象的创建结果,对创建过程的细节关注度相对较低。
二、创建对象的方式不同
- 建造者模式:需要一个指挥者来协调具体建造者逐步构建对象,通常有多个方法参与对象的构建过程,并且可以对构建过程进行精细控制,比如先设置对象的某个属性,再设置另一个属性等。
- 工厂模式:通常是通过一个工厂方法,根据传入的参数直接返回一个完整的对象,创建过程相对简单直接。
三、适用场景不同
- 建造者模式:适用于创建复杂对象,该对象由多个部分组成,且构建过程较为复杂,需要逐步构建并可以灵活配置不同的部分。例如,创建一个具有多个可选配置的汽车对象,包括不同的发动机、内饰、颜色等。
- 工厂模式:适用于对象的创建逻辑相对简单,主要根据不同的条件创建不同类型的对象。比如,根据用户的选择创建不同类型的图形对象(圆形、矩形、三角形等)。
建造者模式使用场景 - 构建各类客户端
建造者模式最常见的应用场景之一是构建各类客户端。例如在进行第三方 API 调用时,像亚马逊的 sp-api、简道云的 ApiClient 以及 SpringAI 的 ChatClient 等的构建,都采用的是建造者模式。这种模式能够更加灵活地配置和创建复杂的客户端对象。
建造者模式使用时机 - 多参数时适用
建造者模式并非在任何情况下都必须使用。主要需考量参数的数量。若仅有一两个参数,且组合方式也仅有一两种,那么此时无需使用建造者模式。然而,当参数数量达到十个左右时,其组合方式可能多达几百万种。在这种情况下,若使用构造器去创建实例,后果将难以想象。而建造者模式则能完美地解决这个问题,可实现任意组合,满足各种复杂的创建需求。
为何多参数场景下选择建造者模式而非使用 set 方法?
原因有以下三点:
- 其一,set 方法通常是 public 对外暴露的,这可能导致对象状态被随意修改,降低了封装性。
- 其二,使用 set 方法不方便添加参数校验逻辑。例如,设置 A 属性时校验需依赖 B 字段,这就对 set 的顺序有要求,而只有经验丰富的 “老司机” 才会清楚这些顺序要求,使用起来极为不便。
- 其三,set 方法会破坏对象的 “不可变对象” 的密封性,使得对象在创建后可能被意外修改,影响程序的稳定性和可维护性。
相比之下,建造者模式在多参数场景下能够更好地控制对象的创建过程,确保对象的完整性和正确性。
建造者模式简单实现 - 简单客户端
建造者的一般格式有:
1、私有构造器
2、构造器传入建造器
3、建造器定义使用static修饰
4、属性值只需写在Builder 中
5、设置属性使用set方法,并返回this
6、Builder 使用build方法创建MyClient 对象,并把建造器设置到MyClient 中
public class MyClient {
private Builder builder;
// 私有构造方法,防止被实例化
private MyClient(Builder builder) {
this.builder = builder;
}
public void sendMessage(String message) {
System.out.println("builder 信息:" + builder.toString());
System.out.println("正在发送消息:" + message);
}
public static class Builder {
private String host;
private Integer port;
private Integer model;
Builder setHost(String host) {
this.host = host;
return this;
}
Builder setPort(Integer port) {
this.port = port;
return this;
}
Builder setModel(Integer model) {
this.model = model;
return this;
}
public MyClient build() {
// 统一校验
if (model == 0) {
System.out.println("model 不能为0");
return null;
}
// 其他校验
return new MyClient(this);
}
@Override
public String toString() {
return "Builder{" +
"host='" + host + '\'' +
", port=" + port +
", model=" + model +
'}';
}
}
}
使用
public static void main(String[] args) {
MyClient myClient = new Builder()
.setHost("host")
.setPort(8080)
.setModel(1)
.build();
myClient.sendMessage("test");
}
执行结果:
builder 信息:Builder{host='host', port=8080, model=1}
正在发送消息:test
创建建造者模式 - 注意的点
1、设置属性不一定需要使用set作为方法前缀,可以不用set也可以使用其他词作为前缀,比如:
OpenAiChatOptions对象属性设置使用with作为前缀。
@GetMapping("/chat/model/fc")
String chatModelFC(@RequestParam(value = "message") String message) {
ChatResponse response = chatModel.call(
new Prompt(
message,
OpenAiChatOptions.builder()
.withFunction("lotteryFunction")
.withModel("gpt-3.5-turbo")
.withTemperature(0.4f)
.build()
));
return response.getResult().getOutput().getContent();
}
Lombok使用@Builder注解快速创建建造者模式
在实体类上直接加@Builder
注解。为了方便测试加了@ToString
注解。
@ToString
@Builder
public class LomBokMyClient {
private String host;
private Integer port;
private Integer model;
}
测试使用
public static void main(String[] args) {
LomBokMyClient client = LomBokMyClient.builder()
.host("localhost")
.port(8080)
.model(1)
.build();
System.out.println(client);
}
执行结果
LomBokMyClient(host=localhost, port=8080, model=1)
下面是使用lombok生成的建造者模式的代码,也能用,但是字段存在了两份,会多占用内存一点。
public class LomBokMyClient {
private String host;
private Integer port;
private Integer model;
@Generated
LomBokMyClient(final String host, final Integer port, final Integer model) {
this.host = host;
this.port = port;
this.model = model;
}
@Generated
public static LomBokMyClient.LomBokMyClientBuilder builder() {
return new LomBokMyClient.LomBokMyClientBuilder();
}
@Generated
public String toString() {
return "LomBokMyClient(host=" + this.host + ", port=" + this.port + ", model=" + this.model + ")";
}
@Generated
public static class LomBokMyClientBuilder {
@Generated
private String host;
@Generated
private Integer port;
@Generated
private Integer model;
@Generated
LomBokMyClientBuilder() {
}
@Generated
public LomBokMyClient.LomBokMyClientBuilder host(final String host) {
this.host = host;
return this;
}
@Generated
public LomBokMyClient.LomBokMyClientBuilder port(final Integer port) {
this.port = port;
return this;
}
@Generated
public LomBokMyClient.LomBokMyClientBuilder model(final Integer model) {
this.model = model;
return this;
}
@Generated
public LomBokMyClient build() {
return new LomBokMyClient(this.host, this.port, this.model);
}
@Generated
public String toString() {
return "LomBokMyClient.LomBokMyClientBuilder(host=" + this.host + ", port=" + this.port + ", model=" + this.model + ")";
}
}
}
总结
建造者模式将复杂对象构建过程分离,与工厂模式有不同用途。适用于多参数构建场景,相比 set 方法有诸多优势。可手动实现,也能用 Lombok 快速创建,为构建复杂对象提供了有效方式。