|
1 | 1 | # 建造者模式 |
2 | 2 |
|
3 | | -建造者模式:封装一个对象的构造过程,并允许按步骤构造。建造者模式可以将一个类的构建和表示进行分离。 |
| 3 | +Builder 模式中文叫作建造者模式,又叫生成器模式,它属于对象创建型模式,是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。 |
4 | 4 |
|
5 | | -### 适用场景: |
| 5 | +在建造者模式中,有如下4种角色: |
6 | 6 |
|
7 | | -- 隔离复杂对象的创建和使用,相同的方法,不同执行顺序,产生不同事件结果 |
8 | | -- 多个部件都可以装配到一个对象中,但产生的运行结果不相同 |
9 | | -- 产品类非常复杂或者产品类因为调用顺序不同而产生不同作用 |
10 | | -- 初始化一个对象时,参数过多,或者很多参数具有默认值 |
11 | | -- Builder模式不适合创建差异性很大的产品类 |
12 | | - 产品内部变化复杂,会导致需要定义很多具体建造者类实现变化,增加项目中类的数量,增加系统的理解难度和运行成本 |
13 | | -- 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性; |
| 7 | +- Product:产品角色 |
| 8 | +- Builder:抽象建造者,定义产品接口 |
| 9 | +- ConcreteBuilder:具体建造者,实现Builder定义的接口,并且返回组装好的产品 |
| 10 | +- Director:指挥者,属于这里面的老大,你需要生产什么产品都直接找它。 |
14 | 11 |
|
15 | | -### 主要作用 |
| 12 | +## 建造者模式应用举例 |
16 | 13 |
|
17 | | -- 用户只需要给出指定复杂对象的类型和内容; |
18 | | -- 建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来) |
| 14 | +### 家装 |
19 | 15 |
|
20 | | -### 两种形式 |
| 16 | +家装不管是精装还是简装,它的流程都相对固定,所以它适用于建造者模式。我们就以家装为例,一起来学习了解一下建造者模式。下图是家装建造者模式简单的 UML 图。 |
21 | 17 |
|
22 | | -建造者有两种形式:传统建造者模式和传统建造者模式变种。 |
| 18 | +### 1、家装对象类 |
23 | 19 |
|
24 | | -传统建造者模式: |
| 20 | +```java |
| 21 | +/** |
| 22 | + * 家装对象类 |
| 23 | + */ |
| 24 | +public class House { |
| 25 | + // 买家电 |
| 26 | + private String jiadian; |
| 27 | + |
| 28 | + // 买地板 |
| 29 | + private String diban; |
| 30 | + // 买油漆 |
| 31 | + private String youqi; |
| 32 | + |
| 33 | + public String getJiadian() { |
| 34 | + return jiadian; |
| 35 | + } |
| 36 | + |
| 37 | + public void setJiadian(String jiadian) { |
| 38 | + this.jiadian = jiadian; |
| 39 | + } |
| 40 | + |
| 41 | + public String getDiban() { |
| 42 | + return diban; |
| 43 | + } |
| 44 | + |
| 45 | + public void setDiban(String diban) { |
| 46 | + this.diban = diban; |
| 47 | + } |
| 48 | + |
| 49 | + public String getYouqi() { |
| 50 | + return youqi; |
| 51 | + } |
| 52 | + |
| 53 | + public void setYouqi(String youqi) { |
| 54 | + this.youqi = youqi; |
| 55 | + } |
| 56 | + |
| 57 | + @Override |
| 58 | + public String toString() { |
| 59 | + return "House{" + |
| 60 | + "jiadian='" + jiadian + '\'' + |
| 61 | + ", diban='" + diban + '\'' + |
| 62 | + ", youqi='" + youqi + '\'' + |
| 63 | + '}'; |
| 64 | + } |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +### 2、抽象建造者 Builder 类 |
25 | 69 |
|
26 | 70 | ```java |
27 | | -public class Computer { |
28 | | - private final String cpu;//必须 |
29 | | - private final String ram;//必须 |
30 | | - private final int usbCount;//可选 |
31 | | - private final String keyboard;//可选 |
32 | | - private final String display;//可选 |
33 | | - |
34 | | - private Computer(Builder builder){ |
35 | | - this.cpu=builder.cpu; |
36 | | - this.ram=builder.ram; |
37 | | - this.usbCount=builder.usbCount; |
38 | | - this.keyboard=builder.keyboard; |
39 | | - this.display=builder.display; |
| 71 | +/** |
| 72 | + * 抽象建造者 |
| 73 | + */ |
| 74 | +public interface HouseBuilder { |
| 75 | + // 买家电 |
| 76 | + void doJiadian(); |
| 77 | + // 买地板 |
| 78 | + void doDiBan(); |
| 79 | + // 买油漆 |
| 80 | + void doYouqi(); |
| 81 | + |
| 82 | + House getHouse(); |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +### 3、具体建造者-简装建造者类 |
| 87 | + |
| 88 | +```java |
| 89 | +/** |
| 90 | + * 简装创建者 |
| 91 | + */ |
| 92 | +public class JianzhuangBuilder implements HouseBuilder { |
| 93 | + |
| 94 | + private House house = new House(); |
| 95 | + |
| 96 | + @Override |
| 97 | + public void doJiadian() { |
| 98 | + house.setJiadian("简单家电就好"); |
40 | 99 | } |
41 | | - public static class Builder{ |
42 | | - private String cpu;//必须 |
43 | | - private String ram;//必须 |
44 | | - private int usbCount;//可选 |
45 | | - private String keyboard;//可选 |
46 | | - private String display;//可选 |
47 | | - |
48 | | - public Builder(String cup,String ram){ |
49 | | - this.cpu=cup; |
50 | | - this.ram=ram; |
51 | | - } |
52 | | - public Builder setDisplay(String display) { |
53 | | - this.display = display; |
54 | | - return this; |
55 | | - } |
56 | | - //set... |
57 | | - public Computer build(){ |
58 | | - return new Computer(this); |
59 | | - } |
| 100 | + |
| 101 | + @Override |
| 102 | + public void doDiBan() { |
| 103 | + house.setDiban("普通地板"); |
| 104 | + } |
| 105 | + |
| 106 | + @Override |
| 107 | + public void doYouqi() { |
| 108 | + house.setYouqi("污染较小的油漆就行"); |
| 109 | + } |
| 110 | + |
| 111 | + @Override |
| 112 | + public House getHouse() { |
| 113 | + return house; |
60 | 114 | } |
61 | 115 | } |
| 116 | +``` |
| 117 | + |
| 118 | +### 4、具体建造者-精装建造者类 |
| 119 | + |
| 120 | +```text |
| 121 | +/** |
| 122 | + * 精装创建者 |
| 123 | + */ |
| 124 | +public class jingzhuangBuilder implements HouseBuilder { |
| 125 | +
|
| 126 | + private House house = new House(); |
| 127 | +
|
| 128 | + @Override |
| 129 | + public void doJiadian() { |
| 130 | + house.setJiadian("二话不说,最好的"); |
| 131 | + } |
| 132 | +
|
| 133 | + @Override |
| 134 | + public void doDiBan() { |
| 135 | + house.setDiban("二话不说,实木地板"); |
| 136 | + } |
62 | 137 |
|
63 | | -public class ComputerDirector { |
64 | | - public void makeComputer(ComputerBuilder builder){ |
65 | | - builder.setUsbCount(); |
66 | | - builder.setDisplay(); |
67 | | - builder.setKeyboard(); |
| 138 | + @Override |
| 139 | + public void doYouqi() { |
| 140 | + house.setYouqi("二话不说,给我来0污染的"); |
| 141 | + } |
| 142 | +
|
| 143 | + @Override |
| 144 | + public House getHouse() { |
| 145 | + return house; |
68 | 146 | } |
69 | 147 | } |
70 | 148 | ``` |
71 | 149 |
|
72 | | -传统建造者模式变种,链式调用: |
| 150 | +### 5、指挥官-家装公司类 |
73 | 151 |
|
74 | 152 | ```java |
75 | | -public class LenovoComputerBuilder extends ComputerBuilder { |
76 | | - private Computer computer; |
77 | | - public LenovoComputerBuilder(String cpu, String ram) { |
78 | | - computer=new Computer(cpu,ram); |
| 153 | +/** |
| 154 | + * 家装公司,值需要告诉他精装还是简装 |
| 155 | + */ |
| 156 | +public class HouseDirector { |
| 157 | + |
| 158 | + public House builder(HouseBuilder houseBuilder){ |
| 159 | + houseBuilder.doDiBan(); |
| 160 | + houseBuilder.doJiadian(); |
| 161 | + houseBuilder.doYouqi(); |
| 162 | + return houseBuilder.getHouse(); |
79 | 163 | } |
80 | | - @Override |
81 | | - public void setUsbCount() { |
82 | | - computer.setUsbCount(4); |
| 164 | +} |
| 165 | +``` |
| 166 | + |
| 167 | +### 6、测试 |
| 168 | + |
| 169 | +```java |
| 170 | +public class App { |
| 171 | + public static void main(String[] args) { |
| 172 | + house(); |
| 173 | + } |
| 174 | + |
| 175 | + public static void house(){ |
| 176 | + HouseDirector houseDirector = new HouseDirector(); |
| 177 | + // 简装 |
| 178 | + JianzhuangBuilder jianzhuangBuilder = new JianzhuangBuilder(); |
| 179 | + System.out.println("我要简装"); |
| 180 | + System.out.println(houseDirector.builder(jianzhuangBuilder)); |
| 181 | + |
| 182 | + // 精装 |
| 183 | + jingzhuangBuilder jingzhuangBuilder = new jingzhuangBuilder(); |
| 184 | + System.out.println("我要精装"); |
| 185 | + System.out.println(houseDirector.builder(jingzhuangBuilder)); |
| 186 | + |
| 187 | + } |
| 188 | +} |
| 189 | +``` |
| 190 | + |
| 191 | +输出结果 |
| 192 | + |
| 193 | +```java |
| 194 | +我要简装 |
| 195 | +House{jiadian="简单家电就好",diban='普通地板 ,youqi= 污染较小的油漆就行' |
| 196 | +我要精装 |
| 197 | +House{jiadian='二话不说,最好的',diban='二话不说,实木地板,yougi='二话不说,给我来0污染的'} |
| 198 | +``` |
| 199 | +
|
| 200 | +我们以家装为例,实现了两个具体的建造者,一个简装建造者、一个精装建造者。我们只需要告诉家装公司,我是需要简装还是精装,他会去帮我们安排,我不需要知道里面具体的细节。 |
| 201 | +
|
| 202 | +### 对象构建 |
| 203 | +
|
| 204 | +在日常开发中,你是不是会经常看到下面这种代码: |
| 205 | +
|
| 206 | +```java |
| 207 | +return new Docket(DocumentationType.SWAGGER_2) |
| 208 | + .apiInfo(apiInfo()) |
| 209 | + .select() |
| 210 | + .apis(RequestHandlerSelectors.basePackage("com.curry.springbootswagger.controller")) |
| 211 | + .paths(PathSelectors.any()) |
| 212 | + .build(); |
| 213 | +``` |
| 214 | +
|
| 215 | +是不是很优美?学会了 Builder 模式之后,你也可以通过这种方式进行对象构建。它是通过变种的 Builder 模式实现的。先不解释了,我们先用 Builder 模式来实现跟上述的对象构建,使用学生类为例。 |
| 216 | +
|
| 217 | +学生对象代码: |
| 218 | +
|
| 219 | +```java |
| 220 | +public class Student { |
| 221 | +
|
| 222 | + private String name; |
| 223 | +
|
| 224 | + private int age; |
| 225 | +
|
| 226 | + private int num; |
| 227 | +
|
| 228 | + private String email; |
| 229 | +
|
| 230 | + // 提供一个静态builder方法 |
| 231 | + public static Student.Builder builder() { |
| 232 | + return new Student.Builder(); |
| 233 | + } |
| 234 | + // 外部调用builder类的属性接口进行设值。 |
| 235 | + public static class Builder{ |
| 236 | + private String name; |
| 237 | +
|
| 238 | + private int age; |
| 239 | +
|
| 240 | + private int num; |
| 241 | +
|
| 242 | + private String email; |
| 243 | +
|
| 244 | + public Builder name(String name) { |
| 245 | + this.name = name; |
| 246 | + return this; |
| 247 | + } |
| 248 | +
|
| 249 | + public Builder age(int age) { |
| 250 | + this.age = age; |
| 251 | + return this; |
| 252 | + } |
| 253 | +
|
| 254 | + public Builder num(int num) { |
| 255 | + this.num = num; |
| 256 | + return this; |
| 257 | + } |
| 258 | +
|
| 259 | + public Builder email(String email) { |
| 260 | + this.email = email; |
| 261 | + return this; |
| 262 | + } |
| 263 | +
|
| 264 | + public Student build() { |
| 265 | + // 将builder对象传入到学生构造函数 |
| 266 | + return new Student(this); |
| 267 | + } |
| 268 | + } |
| 269 | + // 私有化构造器 |
| 270 | + private Student(Builder builder) { |
| 271 | + name = builder.name; |
| 272 | + age = builder.age; |
| 273 | + num = builder.num; |
| 274 | + email = builder.email; |
83 | 275 | } |
84 | | - //... |
| 276 | +
|
85 | 277 | @Override |
86 | | - public Computer getComputer() { |
87 | | - return computer; |
| 278 | + public String toString() { |
| 279 | + return "Student{" + |
| 280 | + "name='" + name + '\'' + |
| 281 | + ", age=" + age + |
| 282 | + ", num=" + num + |
| 283 | + ", email='" + email + '\'' + |
| 284 | + '}'; |
88 | 285 | } |
89 | 286 | } |
| 287 | +``` |
90 | 288 |
|
91 | | -Computer computer=new Computer.Builder("因特尔","三星") |
92 | | - .setDisplay("三星24寸") |
93 | | - .setKeyboard("罗技") |
94 | | - .setUsbCount(2) |
95 | | - .build(); |
| 289 | +调用代码: |
| 290 | + |
| 291 | +```java |
| 292 | +public static void student(){ |
| 293 | + Student student = Student.builder().name("平头哥").num(1).age(18).email("平头哥@163.com").build(); |
| 294 | + System.out.println(student); |
| 295 | + } |
96 | 296 | ``` |
| 297 | + |
| 298 | +可以看到,变种 Builder 模式包括以下内容: |
| 299 | + |
| 300 | +- 在要构建的类内部创建一个静态内部类 Builder |
| 301 | +- 静态内部类的参数与构建类一致 |
| 302 | +- 构建类的构造参数是 静态内部类,使用静态内部类的变量一一赋值给构建类 |
| 303 | +- 静态内部类提供参数的 setter 方法,并且返回值是当前 Builder 对象 |
| 304 | +- 最终提供一个 build 方法构建一个构建类的对象,参数是当前 Builder 对象 |
| 305 | + |
| 306 | +可能你会说,这种写法实现太麻烦了,确实需要我们写很多额外的代码,好在前辈们已经开发出了`lombok`来拯救我们,我们只需要引入`lombok`插件,然后在实体类上添加`@Builder`注解,你就可以使用 Builder 模式构建对象了。 |
| 307 | + |
| 308 | +## 建造者模式的优缺点 |
| 309 | + |
| 310 | +### 优点 |
| 311 | + |
| 312 | +- 在建造者模式中, 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象 |
| 313 | +- 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象 |
| 314 | +- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程 |
| 315 | +- 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则” |
| 316 | + |
| 317 | +### 缺点 |
| 318 | + |
| 319 | +- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。 |
| 320 | +- 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。 |
0 commit comments