• 02.当构造参数过多时使用builder模式


    前言

    《Effective Java》中文第三版,是一本关于Java基础的书,这本书不止一次有人推荐我看。其中包括我很喜欢的博客园博主五月的仓颉,他曾在自己的博文《给Java程序猿们推荐一些值得一看的好书》中也推荐过。加深自己的记忆,同时向优秀的人看齐,决定在看完每一章之后,都写一篇随笔。如果有写的不对的地方、表述的不清楚的地方、或者其他建议,希望您能够留言指正,谢谢。

    《Effective Java》中文第三版在线阅读链接:https://github.com/sjsdfg/effective-java-3rd-chinese/tree/master/docs/notes

    是什么

    builder模式 是创建对象的一种方式。常见的对象创建方式还有 JavaBeans模式、可伸缩方法模式 等。

    哪里用

    扩展到很多可选参数的情景,“很多可选参数”指的是,当你的构造方法入参达到比如四个或更多时。

    一个实体类,你预感它会在后期添加新的字段时,你也可以预先使用builder模式,因为你一开始没有使用,在后期类的参数演化到失控的时候,再想转为builder模式,之前写的代码就会是无用功。

    怎么实现

    我们通过 JavaBeans模式、可伸缩方法模式 与 Builder模式 创建对象,进行对比的方法,来看看Builder的实现,以及它的优点。

    首先,我们来考虑一个场景,一瓶饮料,它的主要原料有很多,例如维生素、水、白砂糖等。我们先用可伸缩方法模式,来实现这样的一个类,代码如下:

    public class Drink {
    
        //水 必填参数
        private final int water;
    
        //糖 必填参数
        private final int sweet;
    
        //维生素 可选参数
        private final int vitamins;
    
        //饮料瓶颜色 可选参数
        private final String color;
    
        public Drink(int water, int sweet){
            this(water, sweet, 0);
        }
    
        public Drink(int water, int sweet, int vitamins){
            this(water, sweet, 0, "白色");
        }
    
        public Drink(int water, int sweet, int vitamins, String color){
            this.water = water;
            this.sweet = sweet;
            this.vitamins = vitamins;
            this.color = color;
        }
    
        public static void main(String[] args) {
            Drink wowHaHa = new Drink(0, 0);
        }
    
    
    }

    当我们想要创建一个对象时,可以用构造方法入参最少的方法来创建(上方的例子最少传入两个参数:水、糖来创建一个Drink类)。通常情况下,可伸缩方法模式 的创建方法让你传入一些你不想设置的参数,但你不得不为它们传递一个值。上面的例子(只有)四个属性的入参,可能看起来不是很糟糕,如果有十个,二十个呢?那它很快就会失控。

    简而言之,可伸缩构造方法模式当有很多参数时,很难编写客户端代码,而且很难读懂它。 

    为了解决'很多参数'这个问题(构造方法中遇到许多可选参数),我们另一种选择是使用 JavaBeans 模式,在这种模式中,调用一个无参的构造方法来创建对象,然后调用 setter 方法来设置每个必需的参数和可选参数,实例代码如下:

    public class Drink02 {
        //水 必填参数
        private int water;
    
        //糖 必填参数
        private int sweet;
    
        //维生素 可选参数
        private int vitamins = -1;
    
        //饮料瓶颜色 可选参数
        private String color = "白色";
        
        public Drink02(){ }
        
        //Setters
        public void setWater(int val) {water = val;}
        public void setSweet(int val) {sweet = val;}
        public void setVitamins(int val) {vitamins = val;}
        public void setColor(String val) {color = val;}
    
        //使用 JavaBeans 模式创建一个对象
        public static void main(String[] args) {
            Drink02 waHaHa = new Drink02();
            waHaHa.setWater(300);
            waHaHa.setSweet(10);
            waHaHa.setVitamins(1);
            waHaHa.setColor("黑色瓶子");
        }
    }

    JavaBeans 模式,没有 可伸缩方法模式 的缺点,但是代码看起来有点冗长,但创建实例很方便,并且生成的代码可读性更高。

    但是,JavaBeans 模式是存在严重缺陷的,由于构造方法被分割成了多次调用,所以在设置属性中(构造过程中) JavaBean 可能处于不一致的状态。该类没有通过检查构造参数参数的有效性来强制一致性的选项。在不一致的状态下尝试使用对象可能会导致一些错误。例如一个需要设置一个必选参数,但是我们并没有设置。幸运的是,我们还有第三种选择:Builder 模式,代码实例如下:

    public class Drink03 {
        //水 必填参数
        private final int water;
    
        //糖 必填参数
        private final int sweet;
    
        //维生素 可选参数
        private final int vitamins;
    
        //饮料瓶颜色 可选参数
        private final String color;
    
        public static class Builder {
            private final int water;
    
            private final int sweet;
    
            private int vitamins = 0;
    
            private String color = "白色";
    
            public Builder(int water, int sweet){
                this.water = water;
                this.sweet = sweet;
            }
    
            public Builder vitamins(int val){
                vitamins = val;
                return this;
            }
    
            public Builder color(String val){
                color = val;
                return this;
            }
    
            public Drink03 build(){
                return new Drink03(this);
            }
        }
    
        private Drink03(Builder builder){
            water    = builder.water;
            sweet    = builder.sweet;
            vitamins = builder.vitamins;
            color    = builder.color;
        }
    
    
        //使用 Builder 模式创建一个对象
        public static void main(String[] args) {
            new Builder(100,10)
                    .vitamins(1).color("黑色").build();
        }
    }

    因为所有默认参数都在一个地方,所以我们的Drink03对象是不可变的,setter赋值的操作,我们在builder方法中已经完成了,这样我们可以进行链式调用。这样的代码,在创建对象的时候非常容易编写,也易于阅读。

    总结

    Builder 模式创建对象

    优点:

    • 更加灵活。Builder灵活的选择了可选参数,也可以自动填充必选参数。
    • 更加安全。

    缺点:

    • 为了创建对象,首先必须创建它的 builder。在看中性能的场合下这可能就是一个问题。而且,builder 模式比伸缩构造方法模式更冗长,因此有在有足够的参数时才值得使用它。

    总而言之,当设计类的构造方法或静态工厂的参数超过几个时,Builder 模式是一个不错的选择,特别是如果许多参数是可选的或相同类型的。builder模式客户端代码比使用 伸缩构造方法 更容易读写,并且builder模式比 JavaBeans 更安全。

  • 相关阅读:
    随机抽样一致性算法(RANSAC)
    RANSAC算法详解
    添加“返回顶部”小图标按钮的JS(JavaScript)代码详解
    vue-cli3组件嵌套
    vue-cli3文件的引入
    vue-cli3组件的使用
    vue ui 使用图形化界面
    vue-cli3及以上版本安装及创建项目
    NetTerm共享文件
    Gin框架配置静态文件static
  • 原文地址:https://www.cnblogs.com/gongguowei01/p/12159168.html
Copyright © 2020-2023  润新知