遇到多个构造器参数时要考虑使用构建器

前言

静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数。对于有很多可选参数的类,程序员一向习惯采用重叠构造器模式,在这种模式下,提供的第一个构造器只有必要的参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数,以此类推,最后一个构造器包含所有可选的参数。

当你想创建实例的时候,就利用参数列表最短的构造器,但该构造器调用通常需要你本不想设置的参数,但还是不得不为它们传递值。随着参数数目的增加,它很快就失去了控制。

简而言之,重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。

JavaBeans模式

遇到许多可选的构造器参数的时候,还有第二种代替方法,即JavaBeans模式,在这种模式下,先调用一个无参构造器来创建对象,然后再调用setter方法来设置每个必要的参数,以及每个相关的可选参数。

这种模式弥补了重叠构造器模式的不足。说的明白一点,就是创建实例很容易,这样产生的代码读起来也很容易。

遗憾的是,JavaBeans模式自身有着很严重的缺点。因为构造过程被分到了几个调用中,在构造过程中JavaBean可能处于不一致的状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。试图使用处于不一致状态的对象将会导致失败,这种失败与包含错误代码大相径庭,因此调试起来十分困难。与此相关的另一点不足在于,JavaBeans模式使得把类做成不可变的可能性不复存在,这就需要程序员付出额外的努力来确保它的线程安全。

建造者模式

它既能保证像重叠构造器模式那样的安全性,也能保证像JavaBeans模式那么好的可读性。他不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成通常是不可变的对象。这个builder通常是它构建的类的静态成员类。代码示例如下:

public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // Required parameters
        private final int servingSize;
        private final int servings;

        // Optional parameters - initialized to default values
        private int calories      = 0;
        private int fat           = 0;
        private int sodium        = 0;
        private int carbohydrate  = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }

        public Builder calories(int val)
        { calories = val;      return this; }
        public Builder fat(int val)
        { fat = val;           return this; }
        public Builder sodium(int val)
        { sodium = val;        return this; }
        public Builder carbohydrate(int val)
        { carbohydrate = val;  return this; }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

注意NutritionFacts是不可变的,所有的默认参数值都单独放在一个地方。builder的设值方法返回builder本身,以便把调用链接起来,得到一个流式的API。下面就是其客户端代码:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                .calories(100).sodium(35).carbohydrate(27).build();

这样的客户端代码很容易编写,更为重要的是易于阅读。Builder模式模拟了具名的可选参数。

Builder模式也适用于类层次结构。使用平行层次结构的builder时,各自嵌套在相应的类中。抽象类有抽象的builder,具体类有具体的builder
每个子类的构建器中的builder方法,都声明返回正确的子类。子类方法声明返回超级类中声明的返回类型的子类型。这被称作协变返回类型。它允许客户端无须转换类型就能使用这些构建器。

builder 和构造器的对比

  • builder可以有多个可变参数。因为builder是利用单独的方法来设置每一个参数。
  • 构造器可以将多次调用某一个方法而传入的参数集中到一个域中。

Builder模式优缺点

优点:

  • Builder模式十分灵活,可以利用单个builder构建多个对象。
  • builder的参数可以在调用builde方法来创建对象期间进行调整,也可以随着不同的对象而改变。
  • builder可以自动填充某些域,例如每次创建对象时自动增加序列号。

缺点:

  • 为了创建对象,必须先创建它的构建器,可能会影响性能。
  • 比重叠构造器模式更加冗长,因此它只在有很多参数的时候才使用。通常最好一开始就是要构建器。

简而言之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是一种不错的选择,特别是当大多数参数都是可选或者类型相同的时候。与使用重叠构造器模式相比,使用Builder模式的客户端代码更易于阅读和编写,构建器也比JavaBeans更加安全。


 上一篇
CSS 实现鼠标悬停弹出图片 CSS 实现鼠标悬停弹出图片
特点 纯 CSS 实现,减少加载 JS 使用 CSS3 transform 属性 实现利用 img 标签将二维码图片放在 a 标签链接中,为了防止跳转 href 超链接写 "javascript:void(0)": &
2019-06-05
下一篇 
用静态工厂方法代替构造器 用静态工厂方法代替构造器
前言对于类而言,为了让客户端获取它自身的一个实例,最传统的方法就是提供一个公有的构造器。还有一种方法,类可以提供一个公有的静态工厂方法,它只是一个返回类的实例的静态方法。 下面是一个来自Boolean的简单示例,这个方法将boolean基本
2019-05-13
  目录