Static factory method and Builder-Chapter II Create and destroy objects-Effective Java study notes 01

The content of the article comes from the book Joshua Bloch-Effective Java (3rd)-2018.chm

Chapter two

Create and unregister objects

Item 1 consider using static factory methods instead of constructors

Static factory method is not the same as the Factory Method pattern from Design Patterns

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

Advantage 1: Static factory method is different from constructor-it has a name

A static factory method with a good name will make the code more readable

Advantage 2: Static factory method is different from constructor-it does not need to create a new object every time it is called

Avoid creating redundant objects

Advantage three: static factory method is different from constructor-it can return a subclass of the returned object

Starting from Java 8, the restriction that interfaces cannot contain static methods has been removed

Advantage 4: The static factory method is used as an input parameter method, and the returned object type can change with the call

The return type can be flexibly controlled in the subclass

Advantage 5: Static factory method When the class contains method is written, the returned object type does not need to exist

This flexible static factory method forms the basis of the service provider framework, such as the Java database connection API (JDBC)

---------Dividing line---------

Disadvantage 1: The main limitation of only providing static factory methods is that there is no public or protected constructor in the class that cannot be subclassed (inherited)

Disadvantage 2: Hard to find for programmers

So you can follow the following naming rules to attract attention

from—A conversion method that takes a parameter and returns an instance of the corresponding type

Date d = Date.from(instant);

of—takes multiple parameters and returns an aggregate method containing instances of these types

Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);

valueOf-more detailed alternative to from and of methods

BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);

instance or getInstance—returns an instance described by its parameters, but cannot have the same value

StackWalker luke = StackWalker.getInstance(options);

create or newInstance—Create or create a new instance like instance or getInstance, but this method guarantees that a new instance is returned every time it is called

Object newArray = Array.newInstance(classObject, arrayLen);

getType—Like getInstance, but the factory method is in a different class, and the type is the type of the object returned by the factory method

FileStore fs = Files.getFileStore(path);

newType—Like newInstance, but the factory method is in a different class, and the type is the type of the object returned by the factory method

BufferedReader br = Files.newBufferedReader(path);

type—a concise alternative to getType and newType

List<Complaint> litany = Collections.list(legacyLitany);

In short, both static factory methods and public constructors have their uses, and it is worthwhile to understand their relative advantages. Usually, a static factory is preferable, so avoid conditional reflection and use public constructors without first considering static factory methods

Item 2 consider a generator mode when faced with many construction parameters

Both static factory methods and constructors have a limitation: they cannot scale well to a large number of optional parameters.
Traditional way

// Telescoping constructor pattern - does not scale well!
public class NutritionFacts {
    private final int servingSize;  // (mL)            required
    private final int servings;     // (per container) required
    private final int calories;     // (per serving)   optional
    private final int fat;          // (g/serving)     optional
    private final int sodium;       // (mg/serving)    optional
    private final int carbohydrate; // (g/serving)     optional

    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }

    public NutritionFacts(int servingSize, int servings,
            int calories) {
        this(servingSize, servings, calories, 0);
    }

    public NutritionFacts(int servingSize, int servings,
            int calories, int fat) {
        this(servingSize, servings, calories, fat, 0);
    }

    public NutritionFacts(int servingSize, int servings,
            int calories, int fat, int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0);
    }

    public NutritionFacts(int servingSize, int servings,
           int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize  = servingSize;
        this.servings     = servings;
        this.calories     = calories;
        this.fat          = fat;
        this.sodium       = sodium;
        this.carbohydrate = carbohydrate;
    }
} 

//当你想创造实例,你使用包含所有你需要的参数最少的构造函数
NutritionFacts cocaCola =  new NutritionFacts(240, 8, 100, 0, 35, 27); 

Usually this kind of constructor is called with some parameters you don't want, but you must pass a parameter anyway. In the above example, pass the value 0 to fat.
In short, the overlapping constructor mode is feasible, but when there are many parameters, It is difficult
to write the code of the client (called client client that implements the API method), and it is more difficult to read it. There are many selected parameters. The second alternative is the JavaBeans mode, in which you call a no-parameter construct The function creates the object, and then calls the setter method to set the required parameters, and each parameter is useful

// JavaBeans Pattern - allows inconsistency, mandates mutability
public class NutritionFacts {
    // Parameters initialized to default values (if any)
    private int servingSize  = -1; // Required; no default value
    private int servings     = -1; // Required; no default value
    private int calories     = 0;
    private int fat          = 0;
    private int sodium       = 0;
    private int carbohydrate = 0;

    public NutritionFacts() { }
    // Setters
    public void setServingSize(int val)  { servingSize = val; }
    public void setServings(int val)    { servings = val; }
    public void setCalories(int val)    { calories = val; }
    public void setFat(int val)         { fat = val; }
    public void setSodium(int val)      { sodium = val; }
    public void setCarbohydrate(int val) { carbohydrate = val; }
} 

//这种模式没有重叠构造函数模式的缺点,创建实例很容易
//即使有点冗长,也很容易阅读生成的代码

NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27); 

Unfortunately, the JavaBeans model has its own serious shortcomings. Since the construction is split into multiple calls, a JavaBean may be in an inconsistent state during the construction process.
This class cannot enforce consistency only by checking the validity of the constructor parameters.
A related disadvantage is that the JavaBeans model excludes the possibility of making a class immutable and requires additional effort by the programmer to ensure thread safety

Fortunately, there is a third alternative that combines the safe JavaBeans pattern with overlapping constructor patterns. It
is the builder pattern [the Builder pattern] The
client does not directly generate the required objects, but uses all the necessary parameters. Call the constructor (or static factory method) and obtain a builder object.
Then, the client calls a setter-like method on the builder object to set each required optional parameter.
Finally, the client calls the parameterless construction method to generate the object. Objects are usually immutable.
Generators are usually static member classes of the class it builds.

For example:

// Builder Pattern
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类是不可变的,所有参数的默认值都在一个地方。
//生成器的setter方法返回生成器本身,以便可以链接调用,从而生成流畅的API。

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

The generator pattern is very suitable for the class hierarchy.
Using the parallel hierarchy of generators, each generator is nested in the corresponding class. Abstract classes have abstract generators; entity classes have entity generators.
For example, consider an abstract class. The bottom of the hierarchy represents different types of pizza

// Builder pattern for class hierarchies
public abstract class Pizza {
   public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
   final Set<Topping> toppings;

   abstract static class Builder<T extends Builder<T>> {
      EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
      public T addTopping(Topping topping) {
         toppings.add(Objects.requireNonNull(topping));
         return self();
      }

      abstract Pizza build();

      // Subclasses must override this method to return "this"
      protected abstract T self();
   }
   Pizza(Builder<?> builder) {
      toppings = builder.toppings.clone(); // See Item  50
   }
} 

For brevity, the sample client code shown below assumes static import of enum constants:

NyPizza pizza = new NyPizza.Builder(SMALL).addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder().addTopping(HAM).sauceInside().build(); 

The generator mode is very flexible. A single generator can be reused to build multiple objects. The parameters of the generator can be adjusted between calling the build method to diversify the created objects. A generator can automatically fill in certain fields when creating an object, such as a serial number that is incremented every time an object is created.
The generator mode also has disadvantages. In order to create an object, its generator must first be created. Although the cost of creating this generator is unlikely to be noticeable in practice, it can be a problem in performance-critical situations.
Also, the generator mode is more verbose than the overlapping constructor mode, so only if there are enough parameters It should be used only when it is worth using, for example, four or more parameters.
In short, when the constructor or static factory method design class will have more than several parameters, especially when many parameters are optional or When the types are the same, the generator mode is a good choice. Compared with the overlapping constructor pattern, generators are easier to read and write client code, and generators are more secure than JavaBeans.

Guess you like

Origin blog.csdn.net/weixin_43596589/article/details/112366758