Usage scenario
This is very important, because if you learn something and don't know what problem to solve, what's the use? The importance of understanding the usage scenario is much higher than whether you will implement this mode, because as long as you know what problems can be solved by using the builder mode, you can complete it after investigating the relevant materials, even if you can't write it. I don't want to say some big and correct terms to confuse you. We only focus on specific problems. As for the thinking of extensibility, you will gradually understand with the growth of your knowledge. Extended reading
When the number of constructor parameters of a class exceeds 4, and some of these parameters are optional parameters, consider using the constructor pattern.
Problems solved
When a class has more than four constructor parameters and some of these parameters are optional, we usually have two ways to build its object. For example, we now have the following Computer class, where cpu and ram are mandatory parameters and the other three are optional parameters. How do we construct instances of this class? There are usually two common methods:
public class Computer { private String cpu;//must private String ram;//must private int usbCount;//Optional private String keyboard;//Optional private String display;//Optional }
First: the folding constructor pattern, which we often use, is shown in the following code
public class Computer { ... public Computer(String cpu, String ram) { this(cpu, ram, 0); } public Computer(String cpu, String ram, int usbCount) { this(cpu, ram, usbCount, "Logitech keyboard"); } public Computer(String cpu, String ram, int usbCount, String keyboard) { this(cpu, ram, usbCount, keyboard, "Samsung display"); } public Computer(String cpu, String ram, int usbCount, String keyboard, String display) { this.cpu = cpu; this.ram = ram; this.usbCount = usbCount; this.keyboard = keyboard; this.display = display; } }
The second is the Java Bean pattern, as shown below
public class Computer { ... public String getCpu() { return cpu; } public void setCpu(String cpu) { this.cpu = cpu; } public String getRam() { return ram; } public void setRam(String ram) { this.ram = ram; } public int getUsbCount() { return usbCount; } ... }
So what are the disadvantages of these two methods?
The first is inconvenient to use and read. You can imagine that when you want to call the constructor of a class, you first have to decide which one to use, and then there are a bunch of parameters. If there are many and the same types of these parameters, you have to find out the meaning of these parameters, which is easy to be confused... Who knows the sour Shuang.
In the second way, the state of the object is easy to change during the construction process, resulting in errors. Because the properties in that class are set step by step, it is easy to make mistakes.
In order to solve these two pain points, the builder pattern was born.
How
1. Create a static internal class Builder in Computer, and then copy all the parameters in Computer into the Builder class.
2. Create a private constructor in Computer with the parameter of Builder type
3. Create a public constructor in Builder. The parameters are those required in Computer, cpu and ram.
4. Create a setting function in Builder, assign values to those optional parameters in Computer, and the return value is an instance of Builder type
5. Create a build() method in Builder, build an instance of Computer and return it
The following code is the final look
public class Computer { private final String cpu;//must private final String ram;//must private final int usbCount;//Optional private final String keyboard;//Optional private final String display;//Optional private Computer(Builder builder){ this.cpu=builder.cpu; this.ram=builder.ram; this.usbCount=builder.usbCount; this.keyboard=builder.keyboard; this.display=builder.display; } public static class Builder{ private String cpu;//must private String ram;//must private int usbCount;//Optional private String keyboard;//Optional private String display;//Optional public Builder(String cup,String ram){ this.cpu=cup; this.ram=ram; } public Builder setUsbCount(int usbCount) { this.usbCount = usbCount; return this; } public Builder setKeyboard(String keyboard) { this.keyboard = keyboard; return this; } public Builder setDisplay(String display) { this.display = display; return this; } public Computer build(){ return new Computer(this); } } //Omit getter method }
How to use
In the client, use chain call to build the object step by step.
Computer computer=new Computer.Builder("intel ","Samsung") .setDisplay("Samsung 24 inch") .setKeyboard("Logitech") .setUsbCount(2) .build();
case
Builder pattern is a very practical and common creative design pattern, such as Glide, a picture processing framework, Retrofit, etc.
extend
In fact, the above content is a simplified way of using Builder in Java. The classic Builder mode is different from it. If you are not interested, you don't have to read it down.
Traditional Builder mode
The UML diagram of the builder pattern is shown below
As shown in the figure above, the builder pattern has four roles.
Product: the final object to be generated, such as a Computer instance.
Builder: the builder's abstract base class (sometimes replaced by an interface). It defines the abstract steps of building a Product, and its body class needs to implement these steps. It will contain a method Product getProduct() to return the final Product.
Concretebuilder: implementation class of builder.
Director: an algorithm that determines how to build the final product It will include a method void Construct(Builder builder) responsible for assembly. In this method, the builder can be set by calling the builder method. After the setting is completed, the final product can be obtained through the builder's getProduct() method.
Next, we will implement the first example in the traditional way.
Step 1: our target Computer class:
public class Computer { private String cpu;//must private String ram;//must private int usbCount;//Optional private String keyboard;//Optional private String display;//Optional public Computer(String cpu, String ram) { this.cpu = cpu; this.ram = ram; } public void setUsbCount(int usbCount) { this.usbCount = usbCount; } public void setKeyboard(String keyboard) { this.keyboard = keyboard; } public void setDisplay(String display) { this.display = display; } @Override public String toString() { return "Computer{" + "cpu='" + cpu + '\'' + ", ram='" + ram + '\'' + ", usbCount=" + usbCount + ", keyboard='" + keyboard + '\'' + ", display='" + display + '\'' + '}'; } }
Step 2: Abstract builder class
public abstract class ComputerBuilder { public abstract void setUsbCount(); public abstract void setKeyboard(); public abstract void setDisplay(); public abstract Computer getComputer(); }
Step 3: entity builder class. We can generate multiple entity builder classes according to the product types to be built. Here, we need to build two brands of computers, Apple Computer and Lenovo computer, so we have generated two entity builder classes.
Mac builder class
public class MacComputerBuilder extends ComputerBuilder { private Computer computer; public MacComputerBuilder(String cpu, String ram) { computer = new Computer(cpu, ram); } @Override public void setUsbCount() { computer.setUsbCount(2); } @Override public void setKeyboard() { computer.setKeyboard("Apple Keyboard"); } @Override public void setDisplay() { computer.setDisplay("Apple monitor"); } @Override public Computer getComputer() { return computer; } }
Lenovo computer builder class
public class LenovoComputerBuilder extends ComputerBuilder { private Computer computer; public LenovoComputerBuilder(String cpu, String ram) { computer=new Computer(cpu,ram); } @Override public void setUsbCount() { computer.setUsbCount(4); } @Override public void setKeyboard() { computer.setKeyboard("Lenovo keyboard"); } @Override public void setDisplay() { computer.setDisplay("Lenovo display"); } @Override public Computer getComputer() { return computer; } }
Step 4: Director
public class ComputerDirector { public void makeComputer(ComputerBuilder builder){ builder.setUsbCount(); builder.setDisplay(); builder.setKeyboard(); } }
use
First generate a director (1), then generate a target builder (2), then use the director to assemble the builder (3), and then use the builder to create a product instance (4).
public static void main(String[] args) { ComputerDirector director=new ComputerDirector();//1 ComputerBuilder builder=new MacComputerBuilder("I5 processor","Samsung 125");//2 director.makeComputer(builder);//3 Computer macComputer=builder.getComputer();//4 System.out.println("mac computer:"+macComputer.toString()); ComputerBuilder lenovoBuilder=new LenovoComputerBuilder("I7 processor","Hynix 222"); director.makeComputer(lenovoBuilder); Computer lenovoComputer=lenovoBuilder.getComputer(); System.out.println("lenovo computer:"+lenovoComputer.toString()); }
The output results are as follows:
mac computer:Computer{cpu='I5 processor', ram='Samsung 125', usbCount=2, keyboard='Apple Keyboard', display='Apple monitor'} lenovo computer:Computer{cpu='I7 processor', ram='Hynix 222', usbCount=4, keyboard='Lenovo keyboard', display='Lenovo display'}
It can be seen that the initial use of the article is a variant of the traditional builder mode. Firstly, it omits the role of director and gives the construction algorithm to the client. Secondly, it writes the builder into the product class to be built, and finally uses chain call.