个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道
如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充
前言
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象
如果经常使用组件,Builder
这个单词是不是很眼熟?很多第三方组件都会使用builder
来构造对象,通过向builder
设置参数,即可或自己想要的对象
重点就在于==设置参数==,我们可以通过设置参数修改目标对象的细节,之前介绍的工厂模式只能选择产品类型,而无法修改参数。
例如我们购买一台DIY组装电脑,配件都是cpu、主板、显卡等等,但是具体配件型号是可以选择的,那么我们只需要将需要的配置告诉技术人员,然后技术人员采购并组装好后,交给我们
在这个例子里,技术人员就是一个建造者,告知其产品参数,然后获得一个产品
1.介绍
使用目的:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示
使用时机:创建复杂对象时,基本部件和创建步骤不变,但其细节经常发生变化的时候
解决问题:在构建一个复杂对象时,有时候需要根据需求调整对象细节,但其基本结构和构造过程都是固定的,通常由各个部分的子对象用一定的算法构成,此时其余创建型模式均无法处理
实现方法:建造者(builder)提供设置参数的入口,在内部根据参数进行组装,并返回出需要的对象
应用实例:
- 快餐店套餐自选,如套餐通常包括汉堡+饮料+小吃,那么不同套餐可以分别选择汉堡、饮料和小吃的种类,快餐店按照需求打包给顾客
- http请求中的
Request.Builder()
,通过设置请求头、url、请求体等参数,生成相应的请求
优点:
- 封装性好,耦合度低,构造与表示分离
- 扩展性好,各个具体的建造者相互独立,便于解耦
- 建造细节对客户端不可见,因而建造者可以细节进行控制和调整,不会影响其他模块
缺点:
- 产品的组件必须相同,适用范围受到限制
- 如果产品内部变化复杂,可能需要很多建造者
- 产品业务发生变化时,建造者也要作出相应修改,后期维护成本较大
2.结构
建造者(Builder)模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成
- 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
- 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法
getResult()
。 - 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 指挥者(Director)(又称导演):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
即,产品用来规范最终产品的内容,抽象建造者负责规范各个零件的创建方法,具体建造者则将这些方法实现,而指挥者负责将这些零件按照流程组装在一起
实际应用中会进行变更和扩展,很多时候会将指挥者抛弃,转由getResult()
和客户端方法进行组装
3.实现
步骤
-
定义产品角色,包括组件和一个全构造方法,重写
toString()
方法以用于打印class Meal { private String food; private String drink; private String pack; private float price; public Meal(String food, String drink, String pack, float price) { this.food = food; this.drink = drink; this.pack = pack; this.price = price; } @Override public String toString() { return "Meal{" + "food='" + food + '\'' + ", drink='" + drink + '\'' + ", pack='" + pack + '\'' + ", price='" + price + '\'' + '}'; } }
-
定义抽象建造者,包括每个组件的创建方法和一个返回产品的方法
abstract class Builder { public abstract void buildFood(); public abstract void buildDrink(); public abstract void buildPaper(); public abstract void buildPrice(); public abstract Meal getResult(); public float calPrice(String food, String drink) throws Exception { float price = 0; if (food.equals("chicken")) { price += 10; } else if (food.equals("beef")) { price += 15; } else { throw new Exception("unknown food"); } if (drink.equals("coca")) { price += 5; } else if (drink.equals("milk")) { price += 6; } else { throw new Exception("unknown drink"); } return price; } }
在这里我定义了一个公用的价格计算器,若每个产品计算规则不同,则定义在具体建造者中
-
定义具体建造者,实现抽象建造者的接口,并根据需求定义组件的创建方法
class ConcreteBuilderA extends Builder { String food; String drink; String pack; float price; @Override public void buildFood() { this.food = "beef"; } @Override public void buildDrink() { this.drink = "coca"; } @Override public void buildPaper() { this.pack = "paper"; } @Override @SneakyThrows public void buildPrice() { this.price = calPrice(food, drink); } public Meal getResult() { return new Meal(food, drink, pack, price); } } class ConcreteBuilderB extends Builder { String food; String drink; String pack; float price; @Override public void buildFood() { this.food = "chicken"; } @Override public void buildDrink() { this.drink = "milk"; } @Override public void buildPaper() { this.pack = "paper"; } @Override @SneakyThrows public void buildPrice() { this.price = calPrice(food, drink); } public Meal getResult() { return new Meal(food, drink, pack, price); } }
这里定义了两个建造者,分别实现抽象构造者的方法
-
定义指挥者,持有建造者,并定义产品构建与组装方法
class Director { private Builder builder; public Director(Builder builder) { this.builder = builder; } public Meal construct() { builder.buildFood(); builder.buildDrink(); builder.buildPaper(); builder.buildPrice(); return builder.getResult(); } }
-
客户端使用
public class BuilderTest { public static void main(String[] args) { Director directorA = new Director(new ConcreteBuilderA()); Meal mealA = directorA.construct(); System.out.println(mealA); Director directorB = new Director(new ConcreteBuilderB()); Meal mealB = directorB.construct(); System.out.println(mealB); } }
完整代码
package com.company.test.builder;
import lombok.SneakyThrows;
class Meal {
private String food;
private String drink;
private String pack;
private float price;
public Meal(String food, String drink, String pack, float price) {
this.food = food;
this.drink = drink;
this.pack = pack;
this.price = price;
}
@Override
public String toString() {
return "Meal{" +
"food='" + food + '\'' +
", drink='" + drink + '\'' +
", pack='" + pack + '\'' +
", price='" + price + '\'' +
'}';
}
}
abstract class Builder {
public abstract void buildFood();
public abstract void buildDrink();
public abstract void buildPaper();
public abstract void buildPrice();
public abstract Meal getResult();
public float calPrice(String food, String drink) throws Exception {
float price = 0;
if (food.equals("chicken")) {
price += 10;
} else if (food.equals("beef")) {
price += 15;
} else {
throw new Exception("unknown food");
}
if (drink.equals("coca")) {
price += 5;
} else if (drink.equals("milk")) {
price += 6;
} else {
throw new Exception("unknown drink");
}
return price;
}
}
class ConcreteBuilderA extends Builder {
String food;
String drink;
String pack;
float price;
@Override
public void buildFood() {
this.food = "beef";
}
@Override
public void buildDrink() {
this.drink = "coca";
}
@Override
public void buildPaper() {
this.pack = "paper";
}
@Override
@SneakyThrows
public void buildPrice() {
this.price = calPrice(food, drink);
}
public Meal getResult() {
return new Meal(food, drink, pack, price);
}
}
class ConcreteBuilderB extends Builder {
private String food;
private String drink;
private String pack;
private float price;
@Override
public void buildFood() {
this.food = "chicken";
}
@Override
public void buildDrink() {
this.drink = "milk";
}
@Override
public void buildPaper() {
this.pack = "paper";
}
@Override
@SneakyThrows
public void buildPrice() {
this.price = calPrice(food, drink);
}
public Meal getResult() {
return new Meal(food, drink, pack, price);
}
}
class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public Meal construct() {
builder.buildFood();
builder.buildDrink();
builder.buildPaper();
builder.buildPrice();
return builder.getResult();
}
}
public class BuilderTest {
public static void main(String[] args) {
Director directorA = new Director(new ConcreteBuilderA());
Meal mealA = directorA.construct();
System.out.println(mealA);
Director directorB = new Director(new ConcreteBuilderB());
Meal mealB = directorB.construct();
System.out.println(mealB);
}
}
执行结果
4.实际应用中的变型
在上述例子中,我们在具体建造者中定义组件内容,显然,我们仍然只能选择在建造者中预设好的组件方案,那如果产品参数无法预知范围呢?
比如,我们需要一份"coke" + "chicken"套餐,咋办,再定义一个builder?显然代码维护成本太高了
因此实际使用中,很多时候都是使用的建造模式与工厂模式结合的方案,我们修改代码如下
代码如下
package com.company.test.builder2;
import lombok.SneakyThrows;
class Meal {
private String food;
private String drink;
private String pack;
private float price;
public Meal(String food, String drink, String pack, float price) {
this.food = food;
this.drink = drink;
this.pack = pack;
this.price = price;
}
@Override
public String toString() {
return "Meal{" +
"food='" + food + '\'' +
", drink='" + drink + '\'' +
", pack='" + pack + '\'' +
", price='" + price + '\'' +
'}';
}
}
abstract class Builder {
public abstract Builder buildFood(String food);
public abstract Builder buildDrink(String drink);
public abstract Meal build();
public float calPrice(String food, String drink) throws Exception {
float price = 0;
if (food.equals("chicken")) {
price += 10;
} else if (food.equals("beef")) {
price += 15;
} else {
throw new Exception("unknown food");
}
if (drink.equals("coca")) {
price += 5;
} else if (drink.equals("milk")) {
price += 6;
} else {
throw new Exception("unknown drink");
}
return price;
}
}
class ConcreteBuilder extends Builder {
private String food="beef";
private String drink="coca";
private String pack="paper";
@Override
public Builder buildFood(String food) {
this.food = food;
return this;
}
@Override
public Builder buildDrink(String drink) {
this.drink = "coca";
return this;
}
@SneakyThrows
public Meal build() {
return new Meal(food, drink, pack, calPrice(food, drink));
}
}
public class BuilderTest {
public static void main(String[] args) {
Meal meal=new ConcreteBuilder()
.buildDrink("coca")
.buildFood("chicken")
.build();
System.out.println(meal);
}
}
是不是这样就眼熟很多很多了?
- 舍弃了指导者
- 在建造者中设置好组件默认值,并提供接口允许修改组件
- 修改组件接口使用链式编程,以精简客户端修改组件时的代码
- 在建造者中提供建造产品方法
- 在客户端设置参数和建造产品
运行结果
鸡肉+可乐的肥仔快乐餐到手~
5.补充
5.1.建造者模式与工厂模式的区别
严格意义上的建造者,更加关注零件的装配,目的是将装配过程与组件的创建分离开来,也就是解耦
工厂模式则将组件的创建和装配一起处理
所以,理论上建造者比工厂模式的耦合度更低,也更利于扩展,但是限制了使用范围(只能预设)且业务变更极其麻烦
上面提到的变型则是对这两种模式的整合,保证了其健壮性(参数可自定义),也将组件的创建和装配分离开了(虽然还在建造者中进行)
5.2.建造者模式常见误区
此处参考自https://blog.csdn.net/poyuan97/article/details/78076593
本来只发现别人的博文写的不太对劲,查阅一番之后果然不对劲。。。
5.2.1.director和builder混淆
单一职责原则:builder负责创建组件,而director负责进行组装
也就是说产品细节由builder控制,director只能调整组装顺序,而==不能看到具体零件的细节,更不能对其进行控制==
这个是在其特性里面就说明了的,但很多文章都违背了这一点(自己随便说搜一下。。一搜一大堆)
5.2.2.builder和product关系混淆
builder对product为依赖关系,而非聚合关系
即,builder依赖于product,因为builder最终要生成一个产品
builder聚合的是产品的部件,而非产品整体
很多文章的builder自行持有一个product整体,再提供方法对product进行修改
也就是说,==builder只能持有产品部件,不可持有产品整体==
5.2.3.实际使用
实际使用中很少严格遵守建造者模式,大部分情况下会对其进行变型和扩展,毕竟设计模式本身就只是提供一种参考的解决方案,并非标准或是准则
但是理解之后才能灵活变通的使用,不然变着变着,可能就不知道原先是啥了
比如上面介绍的变型,实际上就已经不算是严格意义上的建造者模式了
其实还可以进一步扩展,原先的建造者模式只能生产预设的产品,变型后只能生产自定义或者默认参数的产品,我们可以对其进行结合,既有预设,又能自定义,岂不美哉?成年人嘛,当然可以全都要
后记
设计模式是一种前辈们总结出来的经验和参考,而不是设计准则和规范
我们进行学习的目的是为了根据场景进行灵活变通,对其进行变型和扩展,以解决实际需求,而并非模板化的量产代码
当然也别放飞自我,随心所欲,设计模式既然是“最佳”经验,那么必然自身的优点,尽可能的维持其优点,然后根据情况补充其不足和短板才是我们需要学习的
作者:Echo_Ye
WX:Echo_YeZ
EMAIL :[email protected]
个人站点:在搭了在搭了。。。(右键 - 新建文件夹)