【Builder设计模式】
1. 简介
最近在做IM系统的sdk, 要求也是非常的简单,要求sdk简单易用。
简单的要求实现起来可没有那么简单。
这里就先说一下,其中遇到的一个难题:
系统中对象的创建?
在sdk中对象的创建需要精细到每一个属性上,不同于接口可以直接在接口文档中写明传输对象需要传输的字段内容即可,在sdk中不仅仅要写明每一个要传输的对象的字段内容,还需要对每个对象的属性进行验证。
在web应用中,比如IM的服务端中采用的是Jsr303来对传输的对象进行验证。但是sdk只需要采用最基本的Java中的知识点即可,要求就是轻量,简便。
Builder的设计模式针对这种情况便有了很好的解决。
2. Builder的用法
简介:
Builder设计模式又叫创建者模式。简单来说,就是一步步创建一个对象,它对用户屏蔽了里面构建的细节,但是却可以精细的控制对象的构造过程。
简单来说一下,我对这个概念的理解。在面向对象思维中,我们肯定是先创建对象,拥有了对象之后,我们在一步步的往对象中填充具体的属性。
而Builder的设计模式,是先创建对象中的属性,然后再构建对象。整个对象的创建过程逆了过来。
下面结合代码来进行分析:
User user = new User();user.setName("li");
user.setAge(18);
user.setSex("男");
先创建了user对象,然后再调用set方法往其中填充一个个的属性。
或者采用构造方法的形式,提供一个构造方法,在对象创建的时候属性直接填充进去。
User user = new User("li",18,"男");
这两种创建对象的方式都存在问题:
第一种方法,先创建对象再填充属性。假如在创建的过程中,对象创建好了,但是某个属性忘记填充了。这就会导致后面操作的错误,而且如果要进行验证,就只能在调用对象的地方进行验证,就会导致业务代码和功能性代码进行耦合。这种方式肯定不是最好的。而且对于使用简单的要求也没有很好的实现。
第二种方法,在创建对象的时候,我们可以在构造方法中对其中的属性进行验证。比如下面:
public class User{private String name;private int age;private String sex;public User(String name,int age,String sex){if(StrUtil.isBlank(name)){throw new RuntimeException("name not set");}//......}
}
这种创建对象的方式避免了第一种方式的缺点,但是还存在一个致命的缺陷——不够灵活。
比如上面我们要求sex属性,可有可无。上面的有参构造函数就不适用了,我们就需要在添加另外一个构造函数。
public User(String name,int age){this.User(name,age,null);
}
通过添加构造方法的形式,我们能够处理不够灵活的缺陷。但是这个问题其实并没有很好解决,我们只是把这个问题转移到了使用用户哪里,用户在使用起来的时候就要认真选择使用哪一个构造方法。而且一旦对象中的属性多了起来,构造方法也变得会很多了起来。
我们接下来看看Builder设计模式是如何做的?
先看看Builder设计模式的组成部分:
- 在实体类中创建一个xxxBuilder名称的静态内部类,并且和实体类具有相同的属性(称为构建器)。
- 对于实体类中的每个参数,构建器都要创建类似于setter的方法。只不过方法名与该参数名相同,并且返回值是xxxBuilder构建起本身(便于链式调用)。
- 在构建器中,需要创建一个build()方法,调用此方法就会根据设置的值创建实体对象。
- 在实体类中,会创建一个builder()静态方法,它的目的就是用来创建构造器。
举例:
//实体类
public class User{private String name;private int age;private String sex;private User(String name,int age,String sex){this.name = name;this.age = age;this.sex = sex;}//实体类的get方法,方便获取对象的属性public String getName(){return this.name;}public int getAge(){return this.age;}public String getSex(){return this.sex;}//创建builder方法,返回一个构建器public static UserBuilder builder(){return new UserBuilder();}//构建器public static class UserBuilder{//属性和实体类中属性相同private String name;private int age;private String sex;//提供与setter方法类似的功能public UserBuilder name(String name){this.name = name;return this;}public UserBuilder age(int age){this.age = age;return this;}public UserBuilder sex(String sex){this.sex = sex;return this;}//提供一个build方法,创建实体类对象public User build(){return new User(this.name,this.age,this.sex);}}
}
这样一个简单的Builder模式的类就完成了。我们下面来看看如何使用:
public static void mian(String[] args){//调用方式一User user = User.builder().name("li").age(18).sex("男").build();
}
上面就是使用链式拼接的方式来创建User对象了。
剩下的还有两个问题没有解决:
- 对象属性的验证?
- 灵活的创建对象?
针对第一个问题,我们可以对构造器进行如下的扩展。
//构建器
public static class UserBuilder{//属性和实体类中属性相同private String name;private int age;private String sex;//提供与setter方法类似的功能public UserBuilder name(String name){if(StrUtil.isBlank(name)){throw new RuntimeException("name must be not null or blank!");}this.name = name;return this;}public UserBuilder age(int age){if(age==null){throw new RuntimeException("age must be not null or blank!");}this.age = age;return this;}public UserBuilder sex(String sex){if(StrUtil(sex)){throw new RuntimeException("sex must be not null or blank!")}this.sex = sex;return this;}//提供一个build方法,创建实体类对象public User build(){if(StrUtil.isBlank(name)){throw new RuntimeException("name not set!");}if(age==null){throw new RuntimeException("age not set!");}if(StrUtil(sex)){throw new RuntimeException("sex not set!")}return new User(this.name,this.age,this.sex);}
}
这样第一个问题就解决了,把对象的属性验证放在对象的创建过程中,避免了和业务代码的耦合。
针对第二个问题,我们在对构建器进行如下改造:
比如User对象中的sex字段是选填字段。
//构建器
public static class UserBuilder{//属性和实体类中属性相同private String name;private int age;private String sex;//提供与setter方法类似的功能public UserBuilder name(String name){if(StrUtil.isBlank(name)){throw new RuntimeException("name must be not null or blank!");}this.name = name;return this;}public UserBuilder age(int age){if(age==null){throw new RuntimeException("age must be not null or blank!");}this.age = age;return this;}public UserBuilder sex(String sex){this.sex = sex;return this;}//提供一个build方法,创建实体类对象public User build(){if(StrUtil.isBlank(name)){throw new RuntimeException("name not set!");}if(age==null){throw new RuntimeException("age not set!");}if(StrUtil(sex)){this.sex = "未知";}return new User`(this.name,this.age,this.sex);}
}
public static void main(String[] args){//这样调用,灵活性的问题也就迎刃而解了。User user = User.builder().name("li").age(18).build();
}
3. Lombok中的@Builder注解
1. 基础使用
@Builder注解会生成相对复杂的构建器。
@Builder注解可以让我们调用下面的代码来初始化对象的实例:
User.builder().name("admin").age(18).build();
@Builder注解可以放在类,构造函数或方法上。
2. @Builder内部帮我们做了什么?
创建一个名为 xxxBuilder 的内部静态类,并具有和实体类相同的属性(称为构建器)。
在构建器中:对于目标类中的所有的属性和未初始化的 final 字段,都会在构建器中创建对应属性。
在构建器中:创建一个无参的 default 构造函数。
在构建器中:对于实体类中的每个参数,都会对应创建类似于 setter 的方法,只不过方法名与该参数名相同。 并且返回值是构建器本身(便于链式调用)。
在构建器中:一个 build() 方法,调用此方法,就会根据设置的值进行创建实体对象。
在构建器中:同时也会生成一个 toString() 方法。
在实体类中:会创建一个 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 + ")";}}
}
3. 组合用法
@Builder中使用@Singular注释集合
@Builder也可以为集合类型的参数或者属性生成一种特殊的方法,生成的特殊方法可以修改集合中的单个元素(增加一个集合中的元素或者删除集合中的一个元素)。
在@Builder字段注释类,同时使用@Singular注解注释一个集合字段,lombok会为该集合字段生成两个adder放啊,而不是setter方法。
一个是向集合中添加单个元素。
一个是将另一个集合的所有元素添加到集合中。
同时也会生成一个clear方法。
使用了@Singular相对来说是比较复杂的,主要是为了保证以下特性:
- 在调用build()方法时,生成的集合将是不可变的。
- 在调用build()之后调用其中一个adder方法或者clear方法不会修改任何已经生成的对象。如果对集合修改之后,在调用build(),则会创建一个基于上一个对象创建的对象实体。
- 生成的集合将被压缩到最小的可行格式,同时保持高效。
@Singular只能应用于lombok已知的集合类型。
目前支持的类型有:
java.util:
- Iterable,Collection和List(一般情况下,生成不可修改的ArrayList支持)
- Set,SortedSet和NavigableSet(一般情况下,生成可变大小不可修改的HashSet或者TreeSet)
- Map,SortMap和NavigableMap(一般情况下,生成可变大小可不可修改的HashMap或者TreeMap)
来看看使用@Singular直接之后的编译情况
@Builder public class User {private final Integer id;private final String zipCode = "123456";private String username;private String password;@Singularprivate List<String> hobbies; }// 编译后: public class User {private final Integer id;private final String zipCode = "123456";private String username;private String password;private List<String> hobbies;User(Integer id, String username, String password, List<String> hobbies) {this.id = id; this.username = username;this.password = password; this.hobbies = hobbies;}public static User.UserBuilder builder() {return new User.UserBuilder();}public static class UserBuilder {private Integer id;private String username;private String password;private ArrayList<String> hobbies;UserBuilder() {}public User.UserBuilder id(Integer id) { this.id = id; return this; }public User.UserBuilder username(String username) { this.username = username; return this; }public User.UserBuilder password(String password) { this.password = password; return this; }public User.UserBuilder hobby(String hobby) {if (this.hobbies == null) {this.hobbies = new ArrayList();}this.hobbies.add(hobby);return this;}public User.UserBuilder hobbies(Collection<? extends String> hobbies) {if (this.hobbies == null) {this.hobbies = new ArrayList();}this.hobbies.addAll(hobbies);return this;}public User.UserBuilder clearHobbies() {if (this.hobbies != null) {this.hobbies.clear();}return this;}public User build() {List hobbies;switch(this.hobbies == null ? 0 : this.hobbies.size()) {case 0:hobbies = Collections.emptyList();break;case 1:hobbies = Collections.singletonList(this.hobbies.get(0));break;default:hobbies = Collections.unmodifiableList(new ArrayList(this.hobbies));}return new User(this.id, this.username, this.password, hobbies);}public String toString() {return "User.UserBuilder(id=" + this.id + ", username=" + this.username + ", password=" + this.password + ", hobbies=" + this.hobbies + ")";}} }
使用@Singular进行build()来创建实例对象时,并没有直接使用Collections.unmodifiableList(Collection)此方法来创建实例,而是分为三种情况。
第一种:当集合中没有元素时,创建一个空list。
第二种:当集合中存在一个元素时,创建一个不可变的单元素list。
第三种:根据当前集合的元素数量创建对应合适大小的list。
编译后的代码,同时生成了三个关于集合操作的方法:
hobby(String bobby);向集合中添加一个元素
hobbies(Collection<? extends String> hobbies);添加一个集合中的所有元素
clearHobbies();清空当前集合数据
@Singular注解配置value属性
我们先看看@Singular注解的详情:
@Target({FIELD, PARAMETER}) @Retention(SOURCE) public @interface Singular {// 修改添加集合元素的方法名String value() default ""; }
测试如何使用注解属性value
@Builder public class User {private final Integer id;private final String zipCode = "123456";private String username;private String password;@Singular(value = "testHobbies")private List<String> hobbies; }// 测试类 public class BuilderTest {public static void main(String[] args) {User user = User.builder().testHobbies("reading").testHobbies("eat").id(1).password("admin").username("admin").build();System.out.println(user);} }
说明,当我们使用了注解属性 value 之后,我们在使用添加集合元素时的方法名发生相应的改变。但是,同时生成的添加整个集合的方法名发生改变了吗?我们再来看看编译后的代码:
/ 编译后: public class User {// 省略部分代码,只看关键部分public static class UserBuilder {public User.UserBuilder testHobbies(String testHobbies) {if (this.hobbies == null) {this.hobbies = new ArrayList();}this.hobbies.add(testHobbies);return this;}public User.UserBuilder hobbies(Collection<? extends String> hobbies) {if (this.hobbies == null) {this.hobbies = new ArrayList();}this.hobbies.addAll(hobbies);return this;}public User.UserBuilder clearHobbies() {if (this.hobbies != null) {this.hobbies.clear();}return this;}} }
可以看到,只有添加一个元素的方法名发生了改变。
@Builder.Default 的使用
比如有这样一个实体类:
@Builder @ToString public class User {@Builder.Defaultprivate final String id = UUID.randomUUID().toString();private String username;private String password;@Builder.Defaultprivate long insertTime = System.currentTimeMillis(); }
在类中我在 id 和 insertTime 上都添加注解 @Builder.Default ,当我在使用这个实体对象时,我就不需要在为这两个字段进行初始化值,
如下面这样:
public class BuilderTest {public static void main(String[] args) {User user = User.builder().password("admin").username("admin").build();System.out.println(user);} }// 输出内容: User(id=416219e1-bc64-43fd-b2c3-9f8dc109c2e8, username=admin, password=admin, insertTime=1546869309868)
lombok 在实例化对象时就为我们初始化了这两个字段值。
当然,你如果再对这两个字段进行设值的话,那么默认定义的值将会被覆盖掉,如下面这样:
public class BuilderTest {public static void main(String[] args) {User user = User.builder().id("admin").password("admin").username("admin").build();System.out.println(user);} } // 输出内容 User(id=admin, username=admin, password=admin, insertTime=1546869642151)
@Builder 详细配置
下面我们再来详细看看 @Builder 这个注解类地详细实现:
@Target({TYPE, METHOD, CONSTRUCTOR}) @Retention(SOURCE) public @interface Builder {// 如果@Builder注解在类上,可以使用 @Builder.Default指定初始化表达式@Target(FIELD)@Retention(SOURCE)public @interface Default {}// 指定实体类中创建 Builder 的方法的名称,默认为: builder (个人觉得没必要修改)String builderMethodName() default "builder";// 指定 Builder 中用来构件实体类的方法的名称,默认为:build (个人觉得没必要修改)String buildMethodName() default "build";// 指定创建的建造者类的名称,默认为:实体类名+BuilderString builderClassName() default "";// 使用toBuilder可以实现以一个实例为基础继续创建一个对象。(也就是重用原来对象的值)boolean toBuilder() default false;@Target({FIELD, PARAMETER})@Retention(SOURCE)public @interface ObtainVia {// 告诉lombok使用表达式获取值String field() default "";// 告诉lombok使用表达式获取值String method() default "";boolean isStatic() default false;} }
@Builder 全局配置
# 是否禁止使用@Builder lombok.builder.flagUsage = [warning | error] (default: not set) # 是否使用Guaua lombok.singular.useGuava = [true | false] (default: false) # 是否自动使用singular,默认是使用 lombok.singular.auto = [true | false] (default: true)
【Builder设计模式】相关推荐
- matchers依赖_Hamcrest Matchers,Guava谓词和Builder设计模式
matchers依赖 通常,在编码时,我们必须处理其中包含数十个字段的一些POJO对象. 很多时候,我们通过一个带有数十个参数的构造函数来初始化这些类,这以任何可能的想象的方式都是可怕的. 除此之外, ...
- Hamcrest Matchers,Guava谓词和Builder设计模式
通常,在编码时,我们必须处理其中包含数十个字段的一些POJO对象. 很多时候,我们通过一个带有数十个参数的构造函数来初始化这些类,这以任何可能的想象的方式都是可怕的. 除此之外,使用这些构造函数的函数 ...
- 创建设计模式 - Builder设计模式
创建设计模式 - Builder设计模式 今天我们将研究java中的Builder模式.Builder 设计模式是一种创造性的设计模式,如工厂模式和抽象工厂模式. 目录[ 隐藏 ] 1构建器设计模式 ...
- 红橙Darren视频笔记 builder设计模式 navigationbar 导航栏第二版
1.builder设计模式简介 builder的实际应用的典型案例有AlertDialog和OKHttp 例如 // AlertDialogAlertDialog alertDialog = new ...
- 红橙Darren视频笔记 builder设计模式+navigationBar导航条
思路 套用之前的builder设计模式的思路 Product(NavigationBar)+Builder+Parameter 效果: mainActivity代码 @Overrideprotecte ...
- 红橙Darren视频笔记 万能Dialog builder设计模式
1.Android原生的AlertDialog 我们今天看一下AlertDialog的创建方式以及它使用到的builder设计模式 我们先看看原生Android的AlertDialog创建方式: Al ...
- 设计模式(四)Builder设计模式
文章目录 1.定义以及UML建模图: 2.使用场景: 3.核心类 (1) 抽象产品类 computer (2) Builder 抽象Builder,规范产品的组建,一般是由子类实现具体的组建过程. ( ...
- Builder设计模式 构建整个应用的NavigationBar
###1. 概述 每个项目都必须处理头部部分,刚刚开始我们都是在activity布局文件中写一个布局然后findViewById去操作.渐渐的我们开始自定义View然后把自定义的头部写入布局文件中几下 ...
- Builder设计模式
2019独角兽企业重金招聘Python工程师标准>>> Builder模式,又称生成器或构建者模式,属于对象创建型模式,侧重于一步一步的构建复杂对象,只有在构建完成后才会返回生成的对 ...
最新文章
- Hadoop MapReduce编程 API入门系列之最短路径(十五)
- 【OpenCV的C++教程3】掩膜操作的细节
- 机器学习笔记: Upsampling, U-Net, Pyramid Scene Parsing Net
- struts2找不到action_第一次用上Struts2框架做Web开发的体验……
- spring基于XML的AOP-编写必要的代码
- mysql导入三个基本表_mysql 基础导入导出
- MS SQL Server2008大数、小数转varchar
- P5706 【深基2.例8】再分肥宅水(python3实现)
- 我的服务端JS文件合并工具
- 服务器怎么控制忽略样式_看问题要看到本质:从Web服务器说起
- JQuery Smart UI 简介 (一) — 纯Htm+Js的ajax开发框架[演示Demo已放出]
- 华为NP课程笔记19-镜像技术
- 【WPF】资源--《深入浅出WPF》by刘铁锰
- 掉入黑洞会怎样?被拉成面条,还是前往另一个宇宙?
- WPS文件转Excel文件怎么转?建议看看这些方法
- 一文掌握阿里云容器镜像服务ACR
- 使用 HTML、CSS 和 JavaScript 的简单模拟时钟
- 【例16 Java从键盘读入学生成绩,找出最高分,并输出学生成绩等级】
- IFS应用系统-面向服务的架构(SOA)
- 锁屏状态接网络电话,Skype商务 iOS 版推重要功能更新