Design mode [4] - detailed explanation of builder mode

Keywords: Java Design Pattern

Start with a picture and write the rest

introduction

Design pattern collection: http://aphysia.cn/categories/designpattern

If you have used Mybatis, I believe you are familiar with the following code. First create a builder object, and then call the. build() function:

InputStream is = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();

The above is actually the builder mode we want to explain in this article. Let's think about it together.

What is the builder model

Builder pattern is a kind of design pattern, which separates the construction of a complex object from its representation, so that the same construction process can create different representations. (from Baidu Encyclopedia)

Builder mode is actually a kind of creation mode and one of the 23 design modes. It is vague from the above definition, but we have to admit that we really understand it when we have the ability to define something in concise words, because at this time we already know where its boundaries are.

Separating the construction of a complex object from its representation is to abstract the object builder. The construction process is the same, but different constructors can realize different representations.

Structure and examples

The builder mode is mainly divided into the following four roles:

  • Product: a complex object to be constructed by a specific producer
  • Abstract generator: an abstract generator is an interface that creates the interface methods of various parts of a product and the methods that return the product
  • Concrete Builder: implement the interface corresponding to the abstract builder according to its own product characteristics
  • Director: create a complex object to control the specific process

At this point, it may be a little confused. After all, it's all definitions. Let's take the programmer's favorite computer as an example. Suppose we want to produce a variety of computers now. The computer has a screen, mouse, cpu, motherboard, disk, memory, etc., we may be able to write it immediately:

public class Computer {
    private String screen;
    private String mouse;
    private String cpu;
    private String mainBoard;
    private String disk;
    private String memory;
  	...
    public String getMouse() {
        return mouse;
    }

    public void setMouse(String mouse) {
        this.mouse = mouse;
    }

    public String getCpu() {
        return cpu;
    }

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }
  	...
}

In the above example, each attribute uses a separate set method. If different components of different computers are produced, the specific implementation is different. The implementation of such a class seems not very elegant. For example, the construction process of the screens of Lenovo computer and ASUS computer is different, and the construction of these components is theoretically a part of the computer, We can consider pipeline processing.

Of course, there is another implementation, that is, multiple constructors. Different constructors have different parameters and implement optional parameters:

public class Computer {
    private String screen;
    private String mouse;
    private String cpu;
    private String mainBoard;
    private String disk;
    private String memory;

    public Computer(String screen) {
        this.screen = screen;
    }

    public Computer(String screen, String mouse) {
        this.screen = screen;
        this.mouse = mouse;
    }

    public Computer(String screen, String mouse, String cpu) {
        this.screen = screen;
        this.mouse = mouse;
        this.cpu = cpu;
    }
  	...
}

The construction method of the above parameters theoretically meets the requirements of on-demand construction, but there are still deficiencies:

  • If the process of constructing each part is complex, the constructor looks messy
  • If there are multiple requirements for on-demand construction, there are too many constructors
  • Different computer types are constructed and coupled together, which must be abstracted

First, we implement on-demand construction in a pipelined way. We can't overload so many constructors:

public class Computer {
    private String screen;
    private String mouse;
    private String cpu;
    private String mainBoard;
    private String disk;
    private String memory;

    public Computer setScreen(String screen) {
        this.screen = screen;
        return this;
    }

    public Computer setMouse(String mouse) {
        this.mouse = mouse;
        return this;
    }

    public Computer setCpu(String cpu) {
        this.cpu = cpu;
        return this;
    }

    public Computer setMainBoard(String mainBoard) {
        this.mainBoard = mainBoard;
        return this;
    }

    public Computer setDisk(String disk) {
        this.disk = disk;
        return this;
    }

    public Computer setMemory(String memory) {
        this.memory = memory;
        return this;
    }
}

When used, it is constructed like a pipeline. It can be constructed step by step:

        Computer computer = new Computer()
                .setScreen("HD screen")
                .setMouse("Logitech mouse")
                .setCpu("i7 processor")
                .setMainBoard("Lenovo motherboard")
                .setMemory("32G Memory")
                .setDisk("512G disk");

However, the above description is not elegant enough. Since the construction process may be very complex, why not use a specific class to construct it? In this way, the construction process is separated from the main class, and the responsibilities are clearer. Here, the internal class can be used:

package designpattern.builder;

import javax.swing.*;

public class Computer {
    private String screen;
    private String mouse;
    private String cpu;
    private String mainBoard;
    private String disk;
    private String memory;

    Computer(Builder builder) {
        this.screen = builder.screen;
        this.cpu = builder.cpu;
        this.disk = builder.disk;
        this.mainBoard = builder.mainBoard;
        this.memory = builder.memory;
        this.mouse = builder.mouse;
    }

    public static class Builder {
        private String screen;
        private String mouse;
        private String cpu;
        private String mainBoard;
        private String disk;
        private String memory;

        public Builder setScreen(String screen) {
            this.screen = screen;
            return this;
        }

        public Builder setMouse(String mouse) {
            this.mouse = mouse;
            return this;
        }

        public Builder setCpu(String cpu) {
            this.cpu = cpu;
            return this;
        }

        public Builder setMainBoard(String mainBoard) {
            this.mainBoard = mainBoard;
            return this;
        }

        public Builder setDisk(String disk) {
            this.disk = disk;
            return this;
        }

        public Builder setMemory(String memory) {
            this.memory = memory;
            return this;
        }

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

When you use it, use builder to build it. When it is finished, when you call build, assign the specific value to the object we need (here is Computer):

public class Test {
    public static void main(String[] args) {
        Computer computer = new Computer.Builder()
                .setScreen("HD screen")
                .setMouse("Logitech mouse")
                .setCpu("i7 processor")
                .setMainBoard("Lenovo motherboard")
                .setMemory("32G Memory")
                .setDisk("512G disk")
                .build();
        System.out.println(computer.toString());
    }
}

However, according to the above description, if we construct multiple computers, the configuration of each computer is different, and the construction process is also different, then we must abstract the constructor into an abstract class.

First, we define the product class Computer:

public class Computer {
    private String screen;
    private String mouse;
    private String cpu;

    public void setScreen(String screen) {
        this.screen = screen;
    }

    public void setMouse(String mouse) {
        this.mouse = mouse;
    }

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "screen='" + screen + '\'' +
                ", mouse='" + mouse + '\'' +
                ", cpu='" + cpu + '\'' +
                '}';
    }
}

Define an abstract construction class for all computer class constructions:

public abstract class Builder {
    abstract Builder buildScreen(String screen);
    abstract Builder buildMouse(String mouse);
    abstract Builder buildCpu(String cpu);

    abstract Computer build();
}

First build a Lenovo computer, which must implement its own constructor. Each computer always has its own special place:

public class LenovoBuilder extends Builder {
    private Computer computer = new Computer();

    @Override
    Builder buildScreen(String screen) {
        computer.setScreen(screen);
        return this;
    }

    @Override
    Builder buildMouse(String mouse) {
        computer.setMouse(mouse);
        return this;
    }

    @Override
    Builder buildCpu(String cpu) {
        computer.setCpu(cpu);
        return this;
    }

    @Override
    Computer build() {
        System.out.println("Under construction...");
        return computer;
    }
}

With the builder, we also need a commander who is responsible for building our specific computer:

public class Director {
    Builder builder = null;
    public Director(Builder builder){
        this.builder = builder;
    }
    
    public void doProcess(String screen,String mouse,String cpu){
        builder.buildScreen(screen)
                .buildMouse(mouse)
                .buildCpu(cpu); 
    }
}

When using, we only need to build the builder first, and then pass the builder to the commander, who is responsible for the specific construction. After the construction, the builder can call the. build() method to create a computer.

public class Test {
    public static void main(String[] args) {
        LenovoBuilder builder = new LenovoBuilder();
        Director director = new Director(builder);
        director.doProcess("Lenovo screen","Gaming Mouse","High performance cpu");
        Computer computer = builder.build();
        System.out.println(computer);
    }
}

Print results:

Under construction...
Computer{screen='Lenovo screen', mouse='Gaming Mouse', cpu='High performance cpu'}

In fact, the above is the complete Builder mode, but most of what we usually use are directly calling the Builder builder, all the way set(), and finally build(), to create an object.

Usage scenario

What are the benefits of building this model? The first thought should be to decouple the construction process. If the construction process is very complex, it should be written separately, clear and concise. Secondly, the construction of each part can be created independently without multiple construction methods. The construction work is left to the builder rather than the object itself. Professional people do professional things. Similarly, the builder pattern is also applicable to scenarios where different construction methods or construction sequences may produce different construction results.

However, there are still some disadvantages. It is necessary to maintain more Builder objects. If there are few commonalities among multiple products, the abstract Builder will lose its role. If there are many product types, the code will become more complex if too many build classes are defined to implement this change.

Recently, GRPC is used in the company. Almost all the objects in GRPC are based on the builder mode. The chain construction is really comfortable and elegant. The code is written for people. All the design patterns we do are to expand, decouple and avoid that the code can only be passed on from mouth to mouth.

[about the author]:
Qin Huai, the official account of Qin Huai grocery store, is not in the right place for a long time. Personal writing direction: Java source code analysis, JDBC, Mybatis, Spring, redis, distributed, sword finger Offer, LeetCode, etc. I carefully write every article. I don't like the title party and fancy. I mostly write a series of articles. I can't guarantee that what I write is completely correct, but I guarantee that what I write has been practiced or searched for information. Please correct any omissions or mistakes.

Sword finger Offer all questions PDF

What did I write in 2020?

Open source programming notes

Posted by sunilj20 on Wed, 01 Dec 2021 18:46:43 -0800