|
|
|
|
公众号矩阵

建造者模式——不止提高代码档次

建造者模式是一种创建型设计模式, 使你能够分步骤创建复杂对象。它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

作者:海星 来源:JavaKeeper|2021-06-10 19:09

本文转载自微信公众号「JavaKeeper」,作者海星。转载本文请联系JavaKeeper公众号。

简介

Builder Pattern,中文翻译为建造者模式或者构建者模式,也有人叫它生成器模式。

建造者模式是一种创建型设计模式, 使你能够分步骤创建复杂对象。它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。

定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

hello world

程序员麽,先上个 hello world 热热身

  1. public class User { 
  2.  
  3.     private Long id; 
  4.     private String name
  5.     private Integer age;  //可选 
  6.     private String desc; //可选 
  7.  
  8.     private User(Builder builder) { 
  9.         this.id = builder.id; 
  10.         this.name = builder.name
  11.         this.age = builder.age; 
  12.         this.desc = builder.desc
  13.     } 
  14.  
  15.     public static Builder newBuilder(Long id, String name) { 
  16.         return new Builder(id, name); 
  17.     } 
  18.  
  19.     public Long getId() {return id;} 
  20.     public String getName() {return name;} 
  21.     public Integer getAge() {return age;} 
  22.     public String getDesc() {return desc;} 
  23.  
  24.     @Override 
  25.     public String toString() { 
  26.         return "Builder{" + 
  27.                 "id=" + id + 
  28.                 ", name='" + name + '\'' + 
  29.                 ", age=" + age + 
  30.                 ", desc='" + desc + '\'' + 
  31.                 '}'
  32.     } 
  33.  
  34.     public static class Builder { 
  35.         private Long id; 
  36.         private String name
  37.         private Integer age; 
  38.         private String desc
  39.  
  40.         private Builder(Long id, String name) { 
  41.             Assert.assertNotNull("标识不能为空",id); 
  42.             Assert.assertNotNull("名称不能为空",name); 
  43.             this.id = id; 
  44.             this.name = name
  45.         } 
  46.         public Builder age(Integer age) { 
  47.             this.age = age; 
  48.             return this; 
  49.         } 
  50.         public Builder desc(String desc) { 
  51.             this.desc = desc
  52.             return this; 
  53.         } 
  54.         public User build() { 
  55.             return new User(this); 
  56.         } 
  57.  
  58.     } 
  59.  
  60.     public static void main(String[] args) { 
  61.         User user = User.newBuilder(1L, "starfish").age(22).desc("test").build(); 
  62.         System.out.println(user.toString()); 
  63.     } 

这样的代码有什么优缺点呢?

主要优点:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区
  2. 明确了必填参数和可选参数,在构造方法中进行验证;
  3. 可以定义为不可变类,初始化后属性字段值不可变更;
  4. 赋值代码可读性较好,明确知道哪个属性字段对应哪个值;
  5. 支持链式方法调用,相比于调用 Setter 方法,代码更简洁。

主要缺点:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区
  2. 代码量较大,多定义了一个 Builder 类,多定义了一套属性字段,多实现了一套赋值方法;
  3. 运行效率低,需要先创建 Builder 实例,再赋值属性字段,再创建目标实例,最后拷贝属性字段。

当然,以上代码,就可以通过 Lombok 的 @Builder 简化代码

如果我们就那么三三两两个参数,直接构造函数配合 set 方法就能搞定的,就不用套所谓的模式了。

高射炮打蚊子——不合算

假设有这样一个复杂对象, 在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中;甚至还有更糟糕的情况, 那就是这些代码散落在客户端代码的多个位置。

这时候才是构造器模式上场的时候

上边的例子,其实属于简化版的建造者模式,只是为了方便构建类中的各个参数,”正经“的和这个有点差别,更倾向于用同样的构建过程分步创建不同的产品类。

我们接着扯~

结构

从 UML 图上可以看到有 4 个不同的角色

  • 抽象建造者(Builder):创建一个 Produc 对象的各个部件指定的接口/抽象类
  • 具体建造者(ConcreteBuilder):实现接口,构建和装配各个组件
  • 指挥者/导演类(Director):构建一个使用 Builder 接口的对象。负责调用适当的建造者来组建产品,导演类一般不与产品类发生依赖关系,与导演类直接交互的是建造者类。
  • 产品类(Product):一个具体的产品对象

demo

假设我是个汽车工厂,需求就是能造各种车(或者造电脑、造房子、做煎饼、生成不同文件TextBuilder、HTMLBuilder等等,都是一个道理)

1、生成器(Builder)接口声明在所有类型生成器中通用的产品构造步骤

  1. public interface CarBuilder { 
  2.     void setCarType(CarType type); 
  3.     void setSeats(int seats); 
  4.     void setEngine(Engine engine); 
  5.     void setGPS(GPS gps); 

2、具体的生成器(Concrete Builders)提供构造过程的不同实现

  1. public class SportsCarBuilder implements CarBuilder { 
  2.  
  3.     private CarType carType; 
  4.     private int seats; 
  5.     private Engine engine; 
  6.     private GPS gps; 
  7.  
  8.     @Override 
  9.     public void setCarType(CarType type) { 
  10.         this.carType = type; 
  11.     } 
  12.  
  13.     @Override 
  14.     public void setSeats(int seats) { 
  15.         this.seats = seats; 
  16.     } 
  17.  
  18.     @Override 
  19.     public void setEngine(Engine engine) { 
  20.         this.engine = engine; 
  21.     } 
  22.  
  23.     @Override 
  24.     public void setGPS(GPS gps) { 
  25.         this.gps = gps; 
  26.     } 
  27.  
  28.     public Car getResult() { 
  29.         return new Car(carType, seats, engine, gps); 
  30.     } 

3、产品(Products)是最终生成的对象

  1. @Setter 
  2. @Getter 
  3. @ToString 
  4. public class Car { 
  5.  
  6.     private final CarType carType; 
  7.     private final int seats; 
  8.     private final Engine engine; 
  9.     private final GPS gps; 
  10.     private double fuel; 
  11.  
  12.     public Car(CarType carType,int seats,Engine engine,GPS gps){ 
  13.         this.carType = carType; 
  14.         this.seats = seats; 
  15.         this.engine = engine; 
  16.         this.gps = gps; 
  17.     } 

4、主管(Director)类定义调用构造步骤的顺序,这样就可以创建和复用特定的产品配置(Director 类的构造函数的参数是 CarBuilder,但实际上没有实例传递出去作参数,因为 CarBuilder 是接口或抽象类,无法产生对象实例,实际传递的是 Builder 的子类,根据子类类型,决定生产内容)

  1. public class Director { 
  2.  
  3.     public void constructSportsCar(CarBuilder builder){ 
  4.         builder.setCarType(CarType.SPORTS_CAR); 
  5.         builder.setSeats(2); 
  6.         builder.setEngine(new Engine(2.0,0)); 
  7.         builder.setGPS(new GPS()); 
  8.     } 
  9.  
  10.     public void constructCityCar(CarBuilder builder){ 
  11.         builder.setCarType(CarType.CITY_CAR); 
  12.         builder.setSeats(4); 
  13.         builder.setEngine(new Engine(1.5,0)); 
  14.         builder.setGPS(new GPS()); 
  15.     } 
  16.  
  17.     public void constructSUVCar(CarBuilder builder){ 
  18.         builder.setCarType(CarType.SUV); 
  19.         builder.setSeats(4); 
  20.         builder.setEngine(new Engine(2.5,0)); 
  21.         builder.setGPS(new GPS()); 
  22.     } 
  23.  

5、客户端使用(最终结果从建造者对象中获取,主管并不知道最终产品的类型)

  1. public class Client { 
  2.  
  3.     public static void main(String[] args) { 
  4.         Director director = new Director(); 
  5.         SportsCarBuilder builder = new SportsCarBuilder(); 
  6.         director.constructSportsCar(builder); 
  7.  
  8.         Car car = builder.getResult(); 
  9.         System.out.println(car.toString()); 
  10.     } 

适用场景

适用场景其实才是理解设计模式最重要的,只要知道这个业务场景需要什么模式,网上浪~程序员能不会吗

  • 使用建造者模式可避免重叠构造函数的出现。

假设你的构造函数中有 N 个可选参数,那 new 各种实例的时候就很麻烦,需要重载构造函数多次

  • 当你希望使用代码创建不同形式的产品 (例如石头或木头房屋) 时, 可使用建造者模式。

如果你需要创建的各种形式的产品, 它们的制造过程相似且仅有细节上的差异, 此时可使用建造者模式。

  • 使用生成器构造组合树或其他复杂对象。

建造者模式让你能分步骤构造产品。你可以延迟执行某些步骤而不会影响最终产品。你甚至可以递归调用这些步骤, 这在创建对象树时非常方便。

VS 抽象工厂

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心抽象过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而生产一个新的产品。

最后

设计模式,这玩意看简单的例子,肯定能看得懂,主要是结合自己的业务思考怎么应用,让系统设计更完善,懂了每种模式后,可以找找各种框架源码或在 github 搜搜相关内容,看看实际中是怎么应用的。

参考

refactoringguru.cn

【编辑推荐】

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区
  2. 不再 iPhone 独占,苹果 macOS Monterey 和 iPadOS 15 新增低电量模式
  3. 用责任链模式实现 OA 系统中的涨薪流程审批
  4. 设计模式系列之策略模式
  5. 泡图书馆,我想到了享元模式
  6. 小小的单例模式竟然有这么多种写法?
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

带你轻松入门 RabbitMQ

带你轻松入门 RabbitMQ

轻松入门RabbitMQ
共4章 | loong576

12人订阅学习

数据湖与数据仓库的分析实践攻略

数据湖与数据仓库的分析实践攻略

助力现代化数据管理:数据湖与数据仓库的分析实践攻略
共3章 | 创世达人

9人订阅学习

云原生架构实践

云原生架构实践

新技术引领移动互联网进入急速赛道
共3章 | KaliArch

40人订阅学习

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微