23种设计模式之建造者模式
文章目录
- 建造者模式
- 建造者模式与工厂模式有什么区别
- 建造者模式使用场景 - 构建各类客户端
- 建造者模式使用时机 - 多参数时适用
- 为何多参数场景下选择建造者模式而非使用 set 方法?
- 建造者模式简单实现 - 简单客户端
- 创建建造者模式 - 注意的点
- Lombok使用@Builder注解快速创建建造者模式
- 总结
建造者模式
建造者模式将复杂对象构建与其表示分离。通过定义抽象建造者和具体建造者,以及指挥者协调构建过程,可精细控制对象创建。适用于创建复杂对象,提高代码可读性和可维护性,方便创建不同配置的对象。
建造者模式与工厂模式有什么区别
一、目的不同
- 建造者模式:侧重于复杂对象的逐步构建,通过多个步骤来组装一个复杂对象,允许对象在构建过程中有不同的表示形式,更注重对象创建的过程控制。
- 工厂模式:主要目的是根据不同的条件创建不同类型的对象,重点在于对象的创建结果,对创建过程的细节关注度相对较低。
二、创建对象的方式不同
- 建造者模式:需要一个指挥者来协调具体建造者逐步构建对象,通常有多个方法参与对象的构建过程,并且可以对构建过程进行精细控制,比如先设置对象的某个属性,再设置另一个属性等。
- 工厂模式:通常是通过一个工厂方法,根据传入的参数直接返回一个完整的对象,创建过程相对简单直接。
三、适用场景不同
- 建造者模式:适用于创建复杂对象,该对象由多个部分组成,且构建过程较为复杂,需要逐步构建并可以灵活配置不同的部分。例如,创建一个具有多个可选配置的汽车对象,包括不同的发动机、内饰、颜色等。
- 工厂模式:适用于对象的创建逻辑相对简单,主要根据不同的条件创建不同类型的对象。比如,根据用户的选择创建不同类型的图形对象(圆形、矩形、三角形等)。
建造者模式使用场景 - 构建各类客户端
建造者模式最常见的应用场景之一是构建各类客户端。例如在进行第三方 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);}@Overridepublic 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;@GeneratedLomBokMyClient(final String host, final Integer port, final Integer model) {this.host = host;this.port = port;this.model = model;}@Generatedpublic static LomBokMyClient.LomBokMyClientBuilder builder() {return new LomBokMyClient.LomBokMyClientBuilder();}@Generatedpublic String toString() {return "LomBokMyClient(host=" + this.host + ", port=" + this.port + ", model=" + this.model + ")";}@Generatedpublic static class LomBokMyClientBuilder {@Generatedprivate String host;@Generatedprivate Integer port;@Generatedprivate Integer model;@GeneratedLomBokMyClientBuilder() {}@Generatedpublic LomBokMyClient.LomBokMyClientBuilder host(final String host) {this.host = host;return this;}@Generatedpublic LomBokMyClient.LomBokMyClientBuilder port(final Integer port) {this.port = port;return this;}@Generatedpublic LomBokMyClient.LomBokMyClientBuilder model(final Integer model) {this.model = model;return this;}@Generatedpublic LomBokMyClient build() {return new LomBokMyClient(this.host, this.port, this.model);}@Generatedpublic String toString() {return "LomBokMyClient.LomBokMyClientBuilder(host=" + this.host + ", port=" + this.port + ", model=" + this.model + ")";}}
}
总结
建造者模式将复杂对象构建过程分离,与工厂模式有不同用途。适用于多参数构建场景,相比 set 方法有诸多优势。可手动实现,也能用 Lombok 快速创建,为构建复杂对象提供了有效方式。