It’s been a long time since I came back to blog. Why haven’t I written it for a long time? Because I have a little too much demand from the product recently, plus the demand I add to myself, I often have no time to output from 9 to 2. Recently, our company added multi-channel rapid packaging and hot update to the project (it was really difficult to add new technology to the old project, and it took a long time), and studied Jenkins continuous integration packaging (but I did not use the company server, so I cut it for a period of time).

While writing a recent bug in the project, the public Dialog management class in the project looks like this:

As a result, every time a type of Dialog is added, a constructor is added to realize it. As time goes by, this class has more and more attributes and more and more types of construction parameters. The original intention of this class is to encapsulate the use of AlterDialog system, so that some dialogs with the same style can be created in the simplest style. But with the iteration of the product, there are more and more UI styles, and this kind of simple encapsulation is a little hard to do.

This article introduces a simple design pattern called the Builder pattern. Perhaps it can be a good solution to the situation described above in production.

Part of the Builder pattern

The design pattern idea, as I understand it, is itself an idea for solving repetitive code encapsulation. It is the experience of those who came before us. And the Builder in books model looks like this:

You can see that the design pattern consists of four parts:

  1. Product: The Product class describes the final result we want collectively, such as a wrapped Dialog with a list of multiple selection buttons.
  2. Builder: A Builder’s interface definition that defines the steps needed to build a product class, but does not provide an implementation.
  3. ConcreteBuilder: The constructor implementation class, inherited from Builder, provides a specific step for creating a type. It is an extension object that we encapsulate. For example, before the project only had a two-button dialog box, now we have a frame to create with multiple options. To complete the creation of a multi-checkbox Dialog, without unordered changes to the first two.
  4. Director: The creator of the Builder class, who assigns properties to various Builders. Director separates the Builder implementation class from the actual client creation, allowing the actual object creation to focus only on the properties of the object.

Examples of builder patterns

Here’s a look at how these four parts work through an abstract Dota hero creation process:

Let’s start with a DotaHero class. This class is our final Product class, Product, which can be interpreted as a superclass, and the implementation depends on how the builder builds it.

public class DotaHero {
    // Heroes categorize intelligence strength and agility
    public static final int HERO_INTELLECTUAL = 1;
    public static final int HERO_POWEER = 2;
    public static final int HERO_AGLIE = 3;
    @IntDef(value = {HERO_AGLIE, HERO_POWEER, HERO_INTELLECTUAL})
    public @interface HeroType {
    }
    // Hero attributes
    private String heroName;
    private String heroDes;
    private int heroType;
    private HashMap<String, String> heroSkills;

    // Cast the top skill
    public void executeSkill(String key) {
        if (heroSkills.containsKey(key)) {
            System.out.println("Casting" + key + "Skills:+ heroSkills.get(key)); }}...// omit a lot of get set
}
Copy the code

DotaHero is the goal of the builder. The attributes and methods defined here are the most complete. Different builders, through different building steps, assign different heroes to the attributes.

Let’s define an abstract constructor class. This class defines all the properties that the builder needs. The implementation class can be extended arbitrarily.

public abstract class HeroBuilder {
    // The object of the final build
    protected DotaHero hero;
    
    public HeroBuilder(a) {
        // Here we initialize the object in the constructor, but the best way is to put it in the build method.
        hero = new DotaHero();
    }

    
    public HeroBuilder setHeroName(String name) {
        hero.setHeroName(name);
        return this;
    }

    public HeroBuilder setHeroDes(String des) {
        hero.setHeroDes(des);
        return this;
    }


    public HeroBuilder addHeroSkill(String keyName, String skill) {
        hero.addHeroSkill(keyName, skill);
        return this;
    }
    
    // Defines an abstract method of the hero type, which requires subclasses to implement
    protected abstract HeroBuilder setHeroType(a);
    
    public DotaHero build(a) {
        returnhero; }}Copy the code

Now let’s define hero builders in Dota. As we all know, heroes in Dota have intelligence, dexterity and strength, so let’s define three types of builders to build three types of heroes.

/ / intelligence
public class AglieHeroBuilder extends HeroBuilder {
    @Override
    protected HeroBuilder setHeroType(a) {
        hero.setHeroType(DotaHero.HERO_AGLIE);
        return this; }}/ / agile
public class IntellectualHeroBuilder extends HeroBuilder {
    @Override
    protected HeroBuilder setHeroType(a) {
        hero.setHeroType(DotaHero.HERO_INTELLECTUAL);
        return this; }}/ / power
public class PowerHeroBuilder extends HeroBuilder {
    @Override
    protected HeroBuilder setHeroType(a) {
        hero.setHeroType(DotaHero.HERO_POWEER);
        return this; }}Copy the code

There are three types of builders listed above, and only abstract methods are implemented here. In actual production, subclasses can extend unique properties as needed.

With that done, we are ready to use the builder to create the objects we need. But the Builder pattern recommends that we keep the production of the actual object from being exposed to the client. We need to separate the builder from the client through the creator (the class that creates the builder), so that the client can simply declare what builder I want to create what object. This way, when a new builder type comes along, the client still only needs to focus on the above two points.

Let’s look at the definition of creator:

public class Director { private HeroBuilder builder; Public Director(@nonnull HeroBuilder Builder) {this. Builder = Builder; } public DotaHerogetAHero() {
        if(builder ! = null) {return builder.build();
        }
        returnnull; }}Copy the code

The above code exposes several constructors to create the builder. What he’s doing so far is getting builders or creating builders by type, using the getAHero method to get a production object.

When to apply the Builder pattern

Through the above examples, we can understand the role and significance of the components of the first part of the Builder model. So when should we apply the Builder pattern in actual production?

The Builder pattern can be used when:

  • The product objects that need to be generated have a complex internal structure and often contain multiple member attributes.
  • The properties of the product objects to be generated depend on each other and the order in which they are generated needs to be specified.
  • The creation of a builder object is independent of the class that created it. The director class was introduced in the Builder pattern, encapsulating the creation process in the director class instead of the Builder class.
  • Isolate the creation and use of complex objects and enable the same creation process to create different products.

The significance of the Builder pattern is that the Builder pattern creates a complex object step by step, allowing users to build complex objects simply by specifying their type and content, without needing to know the specific build details inside.

## Strengths and weaknesses of the Builder model

Advantages of the Builder model:

  1. In the Builder pattern, the client does not have to know the details of the product’s internal composition, decoupling the product itself from the product creation process so that the same creation process can create different product objects.

  2. Each concrete builder is relatively independent of other concrete builders, so it is easy to replace concrete builders or add new concrete builders, and users can use different concrete builders to get different product objects.

  3. Adding a new concrete builder does not need to modify the code of the original class library, and the creator class is designed for the abstract builder class, so the system is easy to expand and conforms to the “open and close principle”.

Disadvantages of the Builder model:

  1. The products created by the Builder mode generally have more in common and their components are similar. If the differences between the products are large, the builder mode is not suitable for use, so its application scope is limited to a certain extent.
  2. If the internal changes of the product are complex, many concrete builder classes may need to be defined to implement the changes, resulting in a large number of concrete builders.

conclusion

Through the above description I believe you already know what is the builder pattern, we also know that the custom step builders mode, but in the actual development, if a product class, the scope of the change is just different attribute or construction order, then a builder implementation class can finish the work, then we can be omitted after the two roles, That is, the Builder class does step 2 and Step 3 on the side.

The author believes that the best way to learn design mode is to apply it. The examples listed in this paper may be too simple and far from being mastered. The next article will guide you to understand the process of Dialog creation encapsulation in our company’s project and understand it from practice.

Reference links:

Builder model

Chan 2nd edition of Design patterns