Design pattern - builder pattern

Introduction and example of builder mode

Builder pattern is one of the creative patterns, which is very commonly used in object-oriented programming

The key of the builder pattern is to separate the construction process of complex objects from its own representation, so that the same construction process can create different representations.

UML diagram corresponding to builder pattern:

Code implementation:

First define a Product class:

public class Product {
    ArrayList<String> parts = new ArrayList<String>();

    public void add(String part) {
        parts.add(part);
    }

    public void show() {
        System.out.println(parts);
    }
}

Next, we define an abstract Builder class:

public abstract class Builder {
    public abstract void buildPartA();
    public abstract void buildPartB();
    public abstract Product getResult() ;
}

Then, there are specific Builder implementation classes:

public class ConcreteBuilder extends Builder {
    private Product product = new Product();
    public Product getResult() {
        return product;
    }
    @Override
    public void buildPartA() {
        product.add("Build the top half of the product");
    }

    @Override
    public void buildPartB() {
        product.add("Build the bottom half of the product");
    }
}

In addition to the builder class, the Director class controls the production process of the Builder:

public class Director {
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    public void construct() {
        builder.buildPartA();
        builder.buildPartB();
    }
}

Finally, the test code of the client:

Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
director.construct();
Product product = builder.getResult();
product.show();

Let's take a look at the results of the operation:

[Build the top half of the product, Build the bottom half of the product]

Application scenario of builder mode

The builder pattern has a wide range of applications, not only in the Java source code, but also in many third-party frameworks.

For example, the famous network framework OkHttp3, in which OkHttpClent and Request are also implemented by the builder mode. Let's take a look at OkHttpClient and its internal class OkHttpClient Part of the source code of Builder:

// Builder is adopted by default
public OkHttpClient() {
  this(new Builder());
}

// The builder configures distributors, agents, protocols, and custom interceptors
OkHttpClient(Builder builder) {
  this.dispatcher = builder.dispatcher;
  this.proxy = builder.proxy;
  this.protocols = builder.protocols;
  /** Omit large sections of code */
  boolean isTLS = false;
  for (ConnectionSpec spec : connectionSpecs) {
    isTLS = isTLS || spec.isTls();
  }
  /** Omit large sections of code */
  if (interceptors.contains(null)) {
    throw new IllegalStateException("Null interceptor: " + interceptors);
  }
  if (networkInterceptors.contains(null)) {
    throw new IllegalStateException("Null network interceptor: " + networkInterceptors);
  }
}
public static final class Builder {

  public Builder() {
// Default parameters for distributors, protocols, agents
    dispatcher = new Dispatcher();
    protocols = DEFAULT_PROTOCOLS;
    proxySelector = ProxySelector.getDefault();
    if (proxySelector == null) {
      proxySelector = new NullProxySelector();
    }
  }

  Builder(OkHttpClient okHttpClient) {
    // Reverse configuration of distributors, agents, protocols
    this.dispatcher = okHttpClient.dispatcher; 
    this.proxy = okHttpClient.proxy; 
    this.protocols = okHttpClient.protocols;
// Add all custom interceptors and custom network interceptors
    this.interceptors.addAll(okHttpClient.interceptors);
    this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
  }

  // Configure agent
  public Builder proxy(@Nullable Proxy proxy) {
    this.proxy = proxy;
    return this;
  }
  // Add a custom interceptor to the interceptor chain
  public Builder addInterceptor(Interceptor interceptor) {
    if (interceptor == null) throw new IllegalArgumentException("interceptor == null");
    interceptors.add(interceptor);
    return this;
  }
  // Finally, the build() method generates the OkHttpClient object
  public OkHttpClient build() {
    return new OkHttpClient(this);
  }
}

In addition, in the Java source code, the most typical embodiment of the builder pattern is StringBuilder.

As we all know, String cannot be modified, and the objects represented by StringBuffer and StringBuilder classes can be modified multiple times without generating new unused objects. The following is part of the source code of StringBuilder:

/**Appends the specified string to this character sequence*/
@Override
public StringBuilder append(CharSequence s) {
    super.append(s);// Implementation process strategy
    return this;
}
/**Replace this character sequence with its inverted form*/
@Override
public StringBuilder reverse() {
    super.reverse();// Implementation process strategy
    return this;
}

Let's write the following test code:

StringBuilder sb = new StringBuilder("In case of freedom");
sb.append("As long as the doctrine is true");
sb.reverse();
System.out.println(sb);
StringBuilder sb1 = new StringBuilder("In case of freedom");
sb1.reverse();
sb1.append("As long as the doctrine is true");
System.out.println(sb1);

The test results are as follows:

System.out: True meaning is mainly from self to if
System.out: Therefore, as long as the doctrine is true

We can find that different strings can be generated according to different call sequences of different StringBuilder methods.

Related knowledge:

StringBuffer is thread safe and StringBuilder is non thread safe. However, under single thread, StringBuilder has better performance than StringBuffer.

Difference between builder mode and three factory modes

The builder model is most easily confused with the simple factory model, but there is a significant difference between them:

The difference between the Builder mode and the simple engineering mode is that there is one more Builder class in the Builder mode, which greatly increases the flexibility of creating objects. It is applicable to the following scenarios:

  1. When an object is created, the calling sequence of multiple same methods is different, resulting in different results
  2. Creating an object is particularly complex, with many parameters, and many parameters have default values

Okhttpclient Builder embodies scenario 2, while StringBuilder embodies scenario 1

At the same time, the Builder mode creates objects through the Builder chain, which can make the code more concise and easy to understand.

extend

System.out.println("Hello World");

The mechanism behind Hello world:

The compilation principle of Java is to convert hello Java is compiled into Hello.java that can be understood by VM Class, and then converted to bytecode that can be understood by different hardware devices for execution.

The famous bytecode enhancement framework ASM is in hello Java compiled into Hello Class can read and analyze class information, change class behavior, enhance class functions, and even generate a bytecode analysis and operation framework for new classes.

Let's take a look at the relevant code. The mv in the code comes from the MethodVisitor interface of the ASM framework.

// Access the static variable out of the System class whose type is PrintSystem
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
// Accessing the data "Hello World" in the constant pool
mv.visitLdcInsn("Hello World");
// Call the println() method of PrintStream class and pass in the object just obtained as a parameter of String type
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

The function of MethodVisitor, an important part of ASM, is to dynamically generate code by using the builder pattern!

Keywords: Java Design Pattern

Added by JakeTheSnake3.0 on Mon, 03 Jan 2022 07:19:08 +0200