安全且优雅的建造者模式

今天要学习的课题来源于《Effective Java》的第二条:

第二条:遇到多个构造器参数时要考虑使用构建器(Builder)

场景介绍

在一个类中,有几个属性字段是必需的,还有大量的可选字段。在创建这个类的对象时,只会有几个可选字段需要填充。

三种解决方案

作者给出了重叠构造器模式、JavaBeans模式以及建造者(Builder)模式三种可选的解决方案:

重叠构造器模式

提供的第一个构造器只有必要的参数,第二个构造器加上一个可选参数,第三个构造器加上两个可选参数,以此类推…

这种模式下,如果选择的参数并不是连续的,比如说我们只需要构造arg1和arg3的参数,只能调用构造器 Constructor(arg1, arg2, arg3) ,这个arg2还是需要传递值。并且在多个参数类型相同的情况下,这种构造方法很容易造成混乱。

JavaBeans模式

先调用无参构造器把对象构建出来,再通过调用setter方法一个一个设置必要的参数。

这种模式在实际开发中也是比较常用的,弥补了重叠构造器模式的不足,并且提供了很高的可读性。

但是这种模式的缺点在于:对象可能处于不完整状态,缺乏线程安全性:在JavaBeans模式中,使用无参构造器首先创建一个空的对象,然后通过多个setter方法逐步设置对象的属性。在这个过程中,对象在每次调用setter之前都是不完整的,可能存在未设置的必要属性。这意味着在对象完全构建之前,如果它被其他线程访问或使用,可能会导致不一致或不正确的行为。(这里联想到单例模式中用volatile禁止指令重排序,同样是防止其他线程访问到未完全构建的单例)

建造者模式

builder.build() 的时候一次性创建完整对象。既能保证像重叠构造器模式的安全性,又具备JavaBeans模式的优秀可读性。

Builder通常是类的静态成员类:

  • 具备该类的所有字段;
  • 提供一个构造方法,内部可以设置必需参数,从而保证了强制依赖设置;
    • new Class.Builder() 来创建该类的Builder对象
  • 设值方法return设值后的builder本身,从而可以将调用链接起来,得到一个十分易读的流式API;
  • 提供build()方法,调用类中的构造器Constructor(Builder builder)返回对象。

Builder十分灵活,可以利用一个builder来构建出多个对象(一个 builder 可以多次build()

Lombok @Builder

开发中常用Lombok提供的@Builder注解,为被标注类自动创建相应的Builder静态成员,可以通过将属性标记为 @NonNull 来确保在构建对象时这些属性必须被设置。

使用案例

  1. mybatis 中封装 Mapper.xml 每条映射的解析结果到 MappedStatement 时,每个参数并不是必须的,这里就用到了建造者模式。像下面的参数:
    private MappedStatement(Builder builder) {
        this.configuration = builder.configuration;
        this.id = builder.id;
        this.sqlCommandType = builder.sqlCommandType;
        this.parameterType = builder.parameterType;
        this.resultType = builder.resultType;
        this.sql = builder.sql;
        this.parameter = builder.parameter;
    }

    /**
     * 建造者模式
     * 不是每个字段都是必须的
     */
    public static class Builder {
        // 必选参数
        private final Configuration configuration;
        private final String id;
        private final SqlCommandType sqlCommandType;

        // 可选
        private String parameterType;
        private String resultType;
        private String sql;
        private Map<Integer, String> parameter;

        // 必选参数构造
        public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType) {
            this.configuration = configuration;
            this.id = id;
            this.sqlCommandType = sqlCommandType;
        }

        public Builder setParameterType(String parameterType) {
            this.parameterType = parameterType;
            return this;
        }

        public Builder setResultType(String resultType) {
            this.resultType = resultType;
            return this;
        }

        public Builder setSql(String sql) {
            this.sql = sql;
            return this;
        }

        public Builder setParameter(Map<Integer, String> parameter) {
            this.parameter = parameter;
            return this;
        }

        public MappedStatement build() {
            // 必选参数非空示警
            assert this.configuration != null;
            assert this.id != null;
            return new MappedStatement(this);
        }
    }

小结

总之,当类的构造器或者静态工厂方法具有大量可选参数时,可以积极考虑使用Builder模式,提供了安全的构造方式(一步构造)以及优秀的可读性(具名构造)。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇