Effective Java Item2:当构造方法的参数(尤其是可选参数)比较多时使用Builder模式

时间:2022-08-28 12:49:34

 Item2 Consider using a Builder when faced with many constructor parameters

当构造方法(静态工厂方法)包含多个参数时,考虑使用Builder

当一个类的构造方法包含多个参数时,其中只有固定的几个参数是必须的,而其他的参数都是可选的,此时使用构造方法或者静态工厂方法都很不方便。

一般来说,有以下几种方式来对应这种情况:

1. 采用telescoping constructor模式。即提供一个只有必要参数的构造器,第二个构造器有一个可选参数,第三个有两个可选参数,依此类推,最后一个构造器包含所有可选参数。这种模式的缺点在于为客户端(使用API的地方) 编程带来了麻烦,一旦参数顺序错误,将会导致难以发现的错误,另外这样的代码的可读性也非常低。

2. 使用JavaBeans模式。即通过无参构造方法构造实例,然后使用setter方法进行属性的设置。这种方式不会有1中所说的缺点。但是JavaBeans模式有其自身的缺点:由于实例的完整构造过程是通过多次调用完成的,所以就存在实例不一致性(线程并发的情况)的问题。同时,JavaBeans模式阻止了类称为常量类,这就要求客户端进行并发的控制。

[Immutable Class]

The term immutable is used to mean that once an object is created, its content cannot be changed.
"String" class is a best example of an immutable class.
To create a object which is immutable u need to make sure that the following conditions are met.
a) The Class should be final.
b) The Properties of the class should be private and should not have any setter methods.
The above mentioned are bare minimum conditions that needs to be satisfied to make the object immutable.

 

3. 使用Builder模式。Builder Pattern综合了telescoping pattern的安全性和JavaBeans。客户端通过使用必须的参数来构造或者使用静态方法来获取Builder类的实例。然后客户端调用Builder实例的每个类似于setter的方法来设置Builder实例的属性。最后,调用Builder类的build方法来获取一个不变的实例。Builder类是目标类的inner static类。

类实例如下:

Telescoping Constructor Pattern

package com.googlecode.javatips4u.effectivejava.builder;

 

public class TelescopingConstructorPattern {

              public static class Dog {

                            private String name;

                            private int weight;

                            private int height;

                            private int age;

                            private int type;

                            private int price;

 

                            public Dog(String name) {

                                          this.name = name;

                            }

 

                            public Dog(String name, int weight) {

                                          super();

                                          this.name = name;

                                          this.weight = weight;

                            }

 

                            public Dog(String name, int weight, int height) {

                                          super();

                                          this.name = name;

                                          this.weight = weight;

                                          this.height = height;

                            }

 

                            public Dog(String name, int weight, int height, int age) {

                                          super();

                                          this.name = name;

                                          this.weight = weight;

                                          this.height = height;

                                          this.age = age;

                            }

 

                            public Dog(String name, int weight, int height, int age, int type) {

                                          super();

                                          this.name = name;

                                          this.weight = weight;

                                          this.height = height;

                                          this.age = age;

                                          this.type = type;

                            }

 

                            public Dog(String name, int weight, int height, int age, int type,

                                                        int price) {

                                          super();

                                          this.name = name;

                                          this.weight = weight;

                                          this.height = height;

                                          this.age = age;

                                          this.type = type;

                                          this.price = price;

                            }

 

                            public String getName() {

                                          return name;

                            }

 

                            public int getWeight() {

                                          return weight;

                            }

 

                            public int getHeight() {

                                          return height;

                            }

 

                            public int getAge() {

                                          return age;

                            }

 

                            public int getType() {

                                          return type;

                            }

 

                            public int getPrice() {

                                          return price;

                            }

              }

 

              public static void main(String[] args) {

                            Dog houseDog = new Dog("bruce",

                                                        15,//weight 15kg

                                                        89, 2, Type.HOUSE_HOLD,

                                                        25//price 25 US dollars

                                                        );

                            Dog petDog = new Dog("lily",

                                                        250,//it should be the price of the pet dog

                                                        55,3,Type.PET,

                                                        8//while this should be the weight of the small pet dog

                            );

                            System.out.println("The house-keep dog's price is "+houseDog.getPrice());

                            System.out.println("The pet dog's price is"+petDog.getPrice());

                            System.out.println("The house-keep dog's weight is "+houseDog.getWeight());

                            //God, who wants to buy a 250kg puppy dog?

                            System.out.println("The pet dog's weight is"+petDog.getWeight());

              }

 

              public final class Type {

                            public static final int HOUSE_HOLD = 1;

                            public static final int PET = 2;

                            public static final int BLIND_HELP = 3;

              }

}

 

JavaBeans Pattern

package com.googlecode.javatips4u.effectivejava.builder;

 

public class JavaBeansPattern {

 

              public static class Cat {

                            private String name;

                            private int weight;

                            private int height;

                            private int age;

                            private String color;

                            private int price;

 

                            public Cat() {

                            }

 

                            public String getName() {

                                          return name;

                            }

 

                            public int getWeight() {

                                          return weight;

                            }

 

                            public int getHeight() {

                                          return height;

                            }

 

                            public int getAge() {

                                          return age;

                            }

 

                            public int getPrice() {

                                          return price;

                            }

 

                            public String getColor() {

                                          return color;

                            }

 

                            public void setColor(String color) {

                                          this.color = color;

                            }

 

                            public void setName(String name) {

                                          this.name = name;

                            }

 

                            public void setWeight(int weight) {

                                          this.weight = weight;

                            }

 

                            public void setHeight(int height) {

                                          this.height = height;

                            }

 

                            public void setAge(int age) {

                                          this.age = age;

                            }

 

                            public void setPrice(int price) {

                                          this.price = price;

                            }

              }

 

              public static void main(String[] args) {

                            final Cat cat = new Cat();

                            cat.setName("smelly");

                            cat.setColor(Color.WHITE);

                            // someone else may change cat some place else.

                            new Thread(new Runnable() {

                                          public void run() {

                                                        cat.setColor(Color.BLACK);

                                          }

                            }).start();

                            try {

                                          Thread.sleep(1000);// make sure the cat has been changed color

                                          // magically.

                            } catch (InterruptedException e) {

                            }

                            cat.setAge(2);

                            System.out.println("Hello, I am smelly, my color should be "

                                                        + Color.WHITE + ",but now I am " + cat.getColor());

                            System.out.println("what happened to me?");

 

                            // so you should construct smelly like following

                            final Cat smelly = new Cat();

                            synchronized (smelly) {

                                          smelly.setName("smelly");

                                          smelly.setColor(Color.BLACK);

                                          new Thread(new Runnable() {

                                                        public void run() {

                                                                      smelly.setColor(Color.BLACK);

                                                        }

                                          }).start();

                                          try {

                                                        Thread.sleep(1000);// make sure new thread finished.

                                          } catch (InterruptedException e) {

                                          }

                                          smelly.setAge(2);

                                          System.out.println("Hello, I am smelly, my color should be "

                                                                      + Color.BLACK + ",and now I am " + cat.getColor());

                            }

              }

 

              public final class Color {

                            public static final String BROWN = "brown";

                            public static final String WHITE = "white";

                            public static final String BLACK = "black";

              }

}

 

Builder Pattern

package com.googlecode.javatips4u.effectivejava.builder;

 

public class BuilderPattern {

              private String patternName;

              private boolean patternUseful;

              private String fieldA;

              private String fieldB;

 

              private BuilderPattern(Builder builder) {

                            patternName = builder.patternName;

                            patternUseful = builder.patternUseful;

                            fieldA = builder.fieldA;

                            fieldB = builder.fieldB;

              }

 

              public String getPatternName() {

                            return patternName;

              }

 

              public boolean isPatternUseful() {

                            return patternUseful;

              }

 

              public String getFieldA() {

                            return fieldA;

              }

 

              public String getFieldB() {

                            return fieldB;

              }

 

              public static class Builder {

 

                            private String patternName;

                            private boolean patternUseful;

                            private String fieldA;

                            private String fieldB;

 

                            public Builder(String patternName) {

                                          this.patternName = patternName;

                            }

 

                            public Builder setPatternUseful(boolean patternUseful) {

                                          this.patternUseful = patternUseful;

                                          return this;

                            }

 

                            public Builder setFieldA(String fieldA) {

                                          this.fieldA = fieldA;

                                          return this;

                            }

 

                            public Builder setFieldB(String fieldB) {

                                          this.fieldB = fieldB;

                                          return this;

                            }

 

                            public BuilderPattern build() {

                                          return new BuilderPattern(this);

                            }

              }

              public static void main(String[] args) {

                            Builder builder = new Builder("BuilderPattern");

                            BuilderPattern pattern = builder.setFieldA("a").setFieldB("b")

                                                        .setPatternUseful(true).build();

                            System.out.println(pattern.getPatternName());

              }

}

Builder Pattern模拟了多个参数的构造器。和构造方法一样,Builder可以强制它的参数不变,在build方法(有时也写成create)中进行检查。将Builder的参数拷贝给类实例 (客户端实际使用的类)后的检查是非常重要的。检查是针对类实例的成员而非Builder的实例。如果参数发生了任何的改变,那么应该在build方法中抛出IllegalStateException

// 没有明白上面这段话的意思? 强制不变性是什么意思?

另一种强制涉及多个参数的不变性的方式是使setter方法的参数是必须具有不变性的所有的参数。如果不变性不被满足,那么抛出IllegalStateException。这样就可以不必等到最后build的时候才发现异常。

另外BuilderPattern相对于Construtor还有一个小优点就是它可以使用多个可变参数。Constructor和普通方法一样,只可以使用一个varargs,即:

package com.googlecode.javatips4u.effectivejava.builder;

 

public class BuilderPattern {

              private String patternName;

              private boolean patternUseful;

              private String[] fieldA;

              private String[] fieldB;

 

              private BuilderPattern(Builder builder) {

                            patternName = builder.patternName;

                            patternUseful = builder.patternUseful;

                            fieldA = builder.fieldA;

                            fieldB = builder.fieldB;

              }

 

              public String getPatternName() {

                            return patternName;

              }

 

              public boolean isPatternUseful() {

                            return patternUseful;

              }

 

              public String[] getFieldA() {

                            return fieldA;

              }

 

              public String[] getFieldB() {

                            return fieldB;

              }

 

              public static class Builder {

 

                            private String patternName;

                            private boolean patternUseful;

                            private String[] fieldA;

                            private String[] fieldB;

 

                            public Builder(String patternName) {

                                          this.patternName = patternName;

                            }

 

                            public Builder setPatternUseful(boolean patternUseful) {

                                          this.patternUseful = patternUseful;

                                          return this;

                            }

 

                            public Builder setFieldA(String... fieldA) {

                                          this.fieldA = fieldA;

                                          return this;

                            }

 

                            public Builder setFieldB(String... fieldB) {

                                          this.fieldB = fieldB;

                                          return this;

                            }

 

                            public BuilderPattern build() {

                                          return new BuilderPattern(this);

                            }

              }

 

//The variable argument type String of the method setBook must be the last parameter

              public void setFields(String... fieldA, String... fieldB) {

              }

 

              public static void main(String[] args) {

                            Builder builder = new Builder("BuilderPattern");

                            BuilderPattern pattern = builder.setFieldA("a").setFieldB("b")

                                                        .setPatternUseful(true).build();

                            System.out.println(pattern.getPatternName());

              }

}

 

Builder Pattern同时也是非常灵活的。可以使用一个builder来构造多个实例。builder可以自动填充一些成员变量,例如序列号,可以每次创建对象的时候自动增加序列号。

一个已经设置了参数的Builder可以作为一个很好的Abstract Factory。使用泛型的话,下面的Builder足以用于所有的Builder

// A builder for objects of type T

public interface Builder<T> {

    public T build();

}

例如BuilderPattern.Builder可以implements Builder<BuilderPattern>。以Builder实例为参数的方法可以将Builder的实例交给客户端提供,例如:

Tree buildTree(Builder<? extends Node> nodeBuilder) { ... }

客户端提供构建每个NodeBuilder

Builder Pattern的缺点:

I. 在性能要求非常严格的情况下。

II. 参数至少包含4个或者更多时。因为使用了太多的set方法,如果参数很少时,反而有些繁琐。

 

In summary, the Builder pattern is a good choice when designing classes whose constructors or static factories would have more than a handful of parameters, especially if most of those parameters are optional.