专栏首页java技术爱好者教你用构建者(生成器)模式优雅地创建对象

教你用构建者(生成器)模式优雅地创建对象

为什么要用构建者模式

很多博客文章上来就先抛出一个定义,我们不妨反过来问一句为什么要用构建者模式。 首先我们创建一个User类,然后采用有参构造器的方式创建对象。

public class User {

    private String id;

    private String name;

    private String gender;

    private String address;

    private Integer age;

    private String phone;

    //省略无参构造器,有参构造器,getter,setter方法...
}
public static void main(String[] args) throws Exception {
    String id = UUID.randomUUID().toString().replaceAll("-", "");
    User user = new User(id, "张三", "男", "广州天河", 20, "135461852xx");
}

我们通过有参构造器创建对象,并且赋值,看起来没什么问题,因为我们经常看到有人是这样写的。 事实上,如果User对象里面有更多的字段,通过有参构造器去创建对象是很难一眼看出字段具体是什么意思,我们经常要看着User构造器的代码,然后对照顺序才能看出字段的代表什么意思。

public User(String id, String name, String gender, String address, Integer age, String phone) {
    this.id = id;
    this.name = name;
    this.gender = gender;
    this.address = address;
    this.age = age;
    this.phone = phone;
}

比如通过上面这个,我们可以知道第一个参数是id,第二个参数是名字,第三个是性别…

用有参构造器创建对象有什么缺点呢?

这显然不利于代码的维护性,对于不熟悉业务的新入职的员工,如果看到这种方式构建一个对象,估计要看上一会,有些项目我遇过一个构造器十几个参数的,更加离谱。而且一般老代码还不敢乱动他的这个构造器,一不小心你动了构造器里面的一个参数的顺序,直接GG;或者你在他原有的构造器后面加多一个参数,你会发现他很多地方都引用了这个有参构造器,你很多地方都要去修改,是真的恶心。

使用无参构造器,通过setter方法设置属性值

public static void main(String[] args) throws Exception {
    User user = new User();
    user.setId(UUID.randomUUID().toString().replaceAll("-", ""));
    user.setName("张三");
    user.setAge(20);
    user.setGender("男");
    user.setPhone("135461852xx");
    user.setAddress("广州天河");
    out.println(user);
}

上面这样,显然比直接用有参构造器要好很多,因为这样就可以创建对象和赋值分开进行,一眼就可以看出对什么属性值赋值,而且如果加一个字段,我们不需要再每一处都去修改,因为用的是无参构造器,是不是这样写就是万全之计呢? 也不是,因为这样创建对象和赋值是分开的,各个参数的初始化被放到了不同的方法中调用,这会导致严重的线程不安全问题(使用构造器则不会有这个问题),对象在一连串的set方法中,可能会出现状态不一致的情况,这是应该尽量避免的。

通过构建者模式,链式调用构建方法设置属性值

什么是链式编程,就是调用一个方法,返回值是他本身,可以继续调用下一个方法,返回又是他本身,如此调用下去,看上去就像一条链子一样。典型的例子可以看java8新特性的Stream流操作。我们可以使用构建者模式,也能达到这种效果,并且线程安全,而且能直观地看到属性值的意思。总得来说,既保证线程安全,也很具有代码的可读性。先看结果代码:

public static void main(String[] args) throws Exception {
    String id = UUID.randomUUID().toString().replaceAll("-", "");
    User user = UserBuilder.getInstance()
            .newPojo()
            .addId(id)
            .addName("张三")
            .addGender("男")
            .addAge(20)
            .addPhone("135461852xx")
            .addAddress("广州天河")
            .build();
}

怎么实现呢?其实很简单,我们只需要创建一个UserBuilder类即可。代码如下:

public class UserBuilder {

    private User user;

    private UserBuilder() {
    }

    public static UserBuilder getInstance() {
        return new UserBuilder();
    }

    public UserBuilder newPojo() {
        this.user = new User();
        //返回本身
        return this;
    }

    public UserBuilder addId(String id) {
        this.user.setId(id);
        //返回本身
        return this;
    }

    public UserBuilder addName(String name) {
        this.user.setName(name);
        return this;
    }

    public UserBuilder addGender(String gender) {
        this.user.setGender(gender);
        return this;
    }

    public UserBuilder addAge(Integer age) {
        this.user.setAge(age);
        return this;
    }

    public UserBuilder addAddress(String address) {
        this.user.setAddress(address);
        return this;
    }

    public UserBuilder addPhone(String phone) {
        this.user.setPhone(phone);
        return this;
    }

    public User build() {
        return this.user;
    }
}

那么是不是这种方式就是万全之计呢,就一定没有缺点吗?

缺点还是有的

1.代码冗长。如果一个对象的属性很多,那我们在创建一个对象时,链式就会变得很长,但是这也没有办法,无论采用构造器还是builder模式都会很长。如果非要变得简洁一点,那就只有采用原型模式(克隆)等其他方式了。 2.会产生很多Builder类。我们可以放在一个包下统一管理应该问题不大。 第二个缺点实际上可以使用Lombok插件,然后在实体类上使用@Builder注解,就不会产生过多的Builder类了。但是有些公司的技术总监不太建议使用Lombok,那就莫得办法了…

注意点

有很多博客的示范代码,Builder类的addXXX方法会写成setXXX方法,这是一个隐患。因为很多框架,对Setter方法比较敏感,往往会对Setter方法做一些处理,所以Builder类里的设置属性值方法尽量不要用setXXX命名,防止出现一些不明原因的错误。

结束语

一般我们在项目中创建复杂的对象时,建议采用这种构建者模式创建对象。这样可以使代码可读性更好。 在java源码中,我们也可以看到构建者模式的应用。比如在StringBuilder类中:

@Override
public StringBuilder append(CharSequence s) {
    super.append(s);
    return this;
}

/**
 * @throws     IndexOutOfBoundsException {@inheritDoc}
 */
@Override
public StringBuilder append(CharSequence s, int start, int end) {
    super.append(s, start, end);
    return this;
}

@Override
public StringBuilder append(char[] str) {
    super.append(str);
    return this;
}

StringBuilderappend()方法也是通过返回this对象实现链式构建对象,人们经常说这个StringBuilder类线程不安全是因为append()方法没有用synchronized修饰。StringBuffer则用了synchronized修饰,所以就是线程安全的。 还有Mybatis框架中,构建SqlSessionFactory对象是使用SqlSessionFactoryBuilder类进行构建,构建者模式运用非常广泛,非常值得学习。更多的设计模式实战经验的分享,就关注java技术小牛吧。

本文分享自微信公众号 - java技术爱好者(yehongzhi_java),作者:牛九木

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 装饰者模式与IO流的应用

    装饰者模式是一种对象结构型模式。动态地给一个对象添加一些额外的职责,就增加功能来说,装饰者模式比生成子类更为灵活。

    java技术爱好者
  • 面试官:兄弟,讲一下责任链模式

    各位java技术爱好者,我们又见面了! 之前我在面试的时候被问到责任链模式的问题,当时答不上来。这件事就一直在我心里耿耿于怀。相信很多人面试完都有这种体验,哈哈...

    java技术爱好者
  • 5千字的SpringMVC总结,我觉得你会需要

    SpringMVC再熟悉不过的框架了,因为现在最火的SpringBoot的内置MVC框架就是SpringMVC。我写这篇文章的动机是想通过回顾总结一下,重新认识...

    java技术爱好者
  • 泛型

    葆宁
  • SpringBoot 注解原理,自动装配原理,图文并茂,万字长文!

    点进@SpringBootApplication来看,发现@SpringBootApplication是一个组合注解。

    搜云库技术团队
  • 关于Spring属性处理器PropertyResolver以及应用运行环境Environment的深度分析,强大的StringValueResolver使用和解析【享学Spring】

    若直接提PropertyResolver或者StringValueResolver可能很小伙伴会觉得非常的陌生,但是我若提Environment和Embedde...

    YourBatman
  • SpringBoot错误信息处理机制及原理

    ErrorMvcAutoConfiguration这个类存放了所有关于错误信息的自动配置。

    石的三次方
  • 用建造者模式实现链式赋值,代码真清爽

    前段时间写了个项目,一个类的属性那叫一个多啊。刚开始直接写一堆set代码,后来set代码实在是太多了,真心看不下去了,用建造者模式重构了一下,嗯,看起来舒服多了...

    Java识堂
  • Android基于JsBridge封装的高效带加载进度的WebView

    从去年4月项目就一直用起了JsBridge,前面也针对jsBridge使用姿势介绍过一篇入门篇,《Android JsBridge实战 打造专属你的Hybrid...

    开发者技术前线
  • 创建型设计模式:Builder Pattern示例介绍

    在此之前,我们了解了工厂和抽象工厂模式。这些模式很有用。然而,有几个案例需要创建一个非常复杂的对象,它需要不同的步骤和操作。在这种情况下,Builder Pat...

    程序你好

扫码关注云+社区

领取腾讯云代金券