创建型模式之建造者模式

Posted by Night Field's Blog on March 22, 2020

1 概述

前面我们说了工厂模式抽象工厂模式,下面来说说建造者模式(Builder Pattern)。 建造者模式也是一种极为常见的创建型模式,前面提到的两类工厂模式隐藏了类的属性与构造细节,而建造者模式通过Builder类,适当地暴露了类的属性,使得类的创建更具有灵活性和可读性。

2 建造者模式

当一个类的构造函数包含很多参数,或者参数之间有很多种组合(如肯德基的套餐),调用构造函数来创建类将会变得不方便,可读性也很差。对于多种组合的情况,工厂模式也将变得不适用。 在这种情况下,建造者模式提供了一种思路,通过将类的创建委托给建造器(Builder),将类的创建与表示分离,大大地简化了类创建的复杂度。

3 案例

考虑做一个Pizza,为方便起见,让它只包含尺寸,底,馅以及是否加芝士四个属性。看看如何用建造者模式来创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class Test {
    public static void main(String[] args) {
        Pizza.Builder pizzaBuilder = new Pizza.Builder();
        Pizza pizza = pizzaBuilder.size(6)
                .crustType(Pizza.CrustType.THIN)
                .topping("Durian")
                .build();

        pizza.info();
    }
}

public class Pizza {
    private int size;// inch
    private CrustType crustType;
    private String topping;
    private boolean cheese;// optional

    private Pizza(Builder builder) {
        if (builder.size <= 0) {
            throw new IllegalStateException("Invalid pizza size.");
        }
        if (builder.crustType == null) {
            throw new IllegalStateException("Invalid pizza crust type.");
        }
        if (builder.topping == null) {
            throw new IllegalStateException("Invalid pizza topping.");
        }
        this.size = builder.size;
        this.crustType = builder.crustType;
        this.topping = builder.topping;
        this.cheese = builder.cheese;
    }

    public void info() {
        System.out.println("Pizza size: " + size + ", crust type: " + crustType + ", topping: " + topping + ", with cheese: " + cheese);
    }

    public static enum CrustType {
        THIN, THICK
    }

    // inner class to build Pizza
    public static class Builder {
        private int size;
        private CrustType crustType;
        private String topping;
        private boolean cheese = false;

        public Builder size(int size) {
            this.size = size;
            return this;
        }
        public Builder crustType(CrustType crustType) {
            this.crustType = crustType;
            return this;
        }
        public Builder topping(String topping) {
            this.topping = topping;
            return this;
        }
        public Builder cheese(boolean cheese) {
            this.cheese = cheese;
            return this;
        }
        public Pizza build() {
            return new Pizza(this);
        }
    }
}

输出:

1
Pizza size: 6, crust type: THIN, topping: Durian, with cheese: false

UML: UML for builder

首先,将类的构造函数私有化了,创建类只能通过Pizza.Builder来进行。而Pizza.Builder创建类的过程,其实是一个定制化类,逐步给类设置属性的过程。通过链式调用,可以很容易并直观地给类添加需要的属性,可伸缩性很强。想要创建什么类型的Pizza,完全由调用者来决定。

建造者模式非常的常见, 如Calendar.Builder,提供了一种自定义构造Calendar的方式。 如StringBuilder,可以动态,灵活地构造String对象。类似地还有MyBatisAbstractSQL,用直观的方式,逐步地构造一个完整的SQL语句。

4 总结

当某个类有很多个属性,或者属性之间有多种组合(Calendar.Builder),又或者是对象维护了一个状态,需要逐步地调用方法使得该状态变得完整(StringBuilder),请考虑使用建造者模式

文中例子的github地址