建造者模式Builder Pattern
根据案例分析传统模式下可能出现的问题
案例
建造房子,假设可以建造普通房子与高楼两种,建造步骤是: 做地基,做墙,做屋顶
代码示例
- 普通房子与高楼可以看为是两种不同的产品,抽象出公共的接口或抽象类
//抽象建造者(根据产品的不同在各个产品子类中进行具体实现)
abstract class AbstractHouse{
//地基
public abstract void bulidBasic();
//盖墙
public abstract void bulidWall();
//屋顶
public abstract void roofed();
//默认建房子的实现步骤
public void bulid() {
bulidBasic();
bulidWall();
roofed();
}
}
- 创建产品类,“普通房子”,“高楼”,根据产品的不同重新抽象方法
//产品: 普通房子
class CommonHouse extends AbstractHouse{
@Override
public void bulidBasic() {
System.out.println("普通房子做地基");
}
@Override
public void bulidWall() {
System.out.println("普通房子盖墙");
}
@Override
public void roofed() {
System.out.println("普通房子做屋顶");
}
}
//产品:高楼
class HighBuilding extends AbstractHouse{
@Override
public void bulidBasic() {
System.out.println("高楼做地基");
}
@Override
public void bulidWall() {
System.out.println("高楼盖墙");
}
@Override
public void roofed() {
System.out.println("高楼做屋顶");
}
}
- 测试
public class DesignTest {
public static void main(String[]args) {
//创建普通房子
AbstractHouse house = new CommonHouse();
//调用抽象类中的默认方法执行建造
house.bulid();
}
}
分析传统模式下可能存在的问题
将创建产品的过程即构建方法的步骤"–>地基–>砌墙–>屋顶"流程,封装在一个bulid()方法中,没有缓冲层,代码耦合的高,假设后续需要更改构建流程,例如先做屋顶后砌墙,需要更改bulid()方法中的代码
建造者模式示例
实现步骤
- 产品与实现流程,细节剥离创建产品类,也就是House
- 创建抽象建造者HouseBuilder, 根据实现细节的不同创建多个不同的建造者例如:CommonHouse与HighBuilding,通过建造者返回对应的产品
- 创建指挥者HouseDirector,将实现步骤定义在指挥者中,后续如果有更改实现步骤,只需要更改指挥者,例如此处的constructHouse()方法
- 对建造者模式,我的理解是: 创建抽象建造者,提供各个步骤的功能的抽象方法,例如"地基",“砌墙”,"屋顶"等,具体的实现由子类根据需求去完成,例如建普通房子,建高楼等,然后创建一个指挥者,由指挥者提供方法,统一管理每个步骤的先后顺序
代码示例
- 产品
//产品(产品与执行流程剥离,具有抽象化)
class House{
private String basic;
private String wall;
private String roofed;
public String getBasic() {
return basic;
}
public void setBasic(String basic) {
this.basic = basic;
}
public String getWall() {
return wall;
}
public void setWall(String wall) {
this.wall = wall;
}
public String getRoofed() {
return roofed;
}
public void setRoofed(String roofed) {
this.roofed = roofed;
}
}
- 抽象建造者
//抽象的建造者
abstract class HouseBuilder{
//建房流程执行完毕后通过下方bulidHouse()方法返回的产品
private House house = new House();
//建造房子的抽象方法
//地基
public abstract void bulidBasic();
//盖墙
public abstract void bulidWall();
//屋顶
public abstract void roofed();
//默认建房子的实现步骤
public House bulidHouse() {
//可能在上面三个方法执行后会创建出对应的House,最后通过该方法返回
return house;
}
}
- 建造者
//根据产品的不同创建不同的建造者
//普通房子建造者
class CommonHouse extends HouseBuilder{
@Override
public void bulidBasic() {
System.out.println("普通房子做地基");
}
@Override
public void bulidWall() {
System.out.println("普通房子盖墙");
}
@Override
public void roofed() {
System.out.println("普通房子做屋顶");
}
}
//建造者:高楼
class HighBuilding extends HouseBuilder{
@Override
public void bulidBasic() {
System.out.println("高楼做地基");
}
@Override
public void bulidWall() {
System.out.println("高楼盖墙");
}
@Override
public void roofed() {
System.out.println("高楼做屋顶");
}
}
- 指挥者
//指挥者,将建房流程步骤交由指挥者,指挥者需要持一个建造者
//在指挥者中通过持有的建造者的不同执行不同的流程,
//执行完毕后通过持有者返回产品
class HouseDirector{
HouseBuilder houseBuilder = null;
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
//将建房的流程交给指挥者,在该方法中通过持有的建造者调用建造者自己的方法
//后续如果更改建造流程,只需要更改此处即可
public House constructHouse() {
houseBuilder.bulidBasic();
houseBuilder.bulidWall();
houseBuilder.roofed();
return houseBuilder.bulidHouse();
}
}
- 测试
public class DesignTest {
public static void main(String[]args) {
/**
* 建造者模式下的角色
* 1.产品角色: 即具体产品,在建造者模式中产品与制造流程剥离,产品抽象化
* 2.抽象建造者: 创建出产品的抽象
* 3.具体建造者: 抽象建造者的具体实现类,根据产品的不同可能存在多个,
* 4.指挥者: 持有建造者,定义了建造的执行步骤,通过持有的建造者,执行步骤,
* 根据持有的建造者不同返回不同返回不同的结果
*/
//1.根据续签建造者对象普通房子CommonHouse (建造者有共同的抽象HouseBuilder
//,通过建造者可以返回创建返回产品)
HouseBuilder houseBuilder = new CommonHouse();
//2.创建指挥者
HouseDirector houseDirector = new HouseDirector (houseBuilder);
//3.指挥者中定义了建造流程方法,执行完毕后,通过指挥者持有的建造者返回产品
House house = houseDirector.constructHouse();
}
}
JDK 建造者模式案例
在创建StringBuilder时,会调用append()方法,该方法在Appendable中为抽象方法,可以将Appendable看为抽象建造者, AbstractStringBuilder实现了Appendable接口并重写了appernd()方法,可以看为建造者,SpringBuilder继承了AbstractStringBuilder通过调用append()方法创建出对象,可以看为指挥者与具体的建造者
总结
- 客户端不必知道产品内部实现步骤组成细节,将产品本身与产品测创建过程解耦
- 根据产品实现的不同创建出不同的建造者,可以方法修改,增加新的建造者,
- 在建造者中更加方便的控制实现步骤,例如修改建造顺序,先做屋顶后砌墙等
业务与建造者模式落地
- 与传统的直接构建类实例的方式相比,建造者模式有以下优点:
- 易于构建复杂对象:建造者模式能够将一个复杂对象的构建过程分解为多个简单的步骤,使得构建过程更加清晰和容易管理。
- 可以控制对象的构建流程:通过Director类来协调Builder类的构建过程,可以确保每个步骤按照正确的顺序执行,并且可以在构建过程中进行检查和控制,从而保证构建出来的对象的质量。
- 可以灵活地改变对象的内部表示:由于建造者模式将对象的构建过程与其最终的表现形式解耦,因此可以方便地改变对象的内部表示方式,而不会影响到客户端代码。
- 更好地支持可变参数构建:如果使用传统的构建方式,为了支持可变参数构建,需要提供多个构造函数或者参数列表非常长的构造函数,这不仅增加了代码的冗余度,而且也降低了代码的可读性。而使用建造者模式,可以通过链式调用的方式方便地添加和移除不同的属性,使代码更加简洁和易读。
1. 案例1
- 在餐饮业务定制菜单中,客户可以按照自己的需求和口味喜好定制菜品,此时可以使用建造者模式。我们可以使用不同的Builder类来构建不同类型的菜品,如主食Builder、饮料Builder、配菜Builder等,并通过一个Director类来指导Builder如何组装,最终生成一份完整的定制菜单
- 定义一个菜品类,用来存储菜品的各种属性
public class Dish {
private String name; // 菜品名称
private double price; // 菜品价格
private String category; // 菜品类别
// 其他getter和setter方法
}
- 定义一个Director类,用来协作组装不同Builder类
public class MenuDirector {
private DishBuilder dishBuilder; // 主食Builder
private DrinkBuilder drinkBuilder; // 饮料Builder
private SideDishBuilder sideDishBuilder; // 配菜Builder
public MenuDirector(DishBuilder dishBuilder, DrinkBuilder drinkBuilder, SideDishBuilder sideDishBuilder) {
this.dishBuilder = dishBuilder;
this.drinkBuilder = drinkBuilder;
this.sideDishBuilder = sideDishBuilder;
}
public Dish makeDish() {
Dish dish = new Dish();
dish.setName(dishBuilder.getName());
dish.setPrice(dishBuilder.getPrice());
dish.setCategory(dishBuilder.getCategory());
dish.setName(drinkBuilder.getName());
dish.setPrice(drinkBuilder.getPrice());
dish.setCategory(drinkBuilder.getCategory());
dish.setName(sideDishBuilder.getName());
dish.setPrice(sideDishBuilder.getPrice());
dish.setCategory(sideDishBuilder.getCategory());
return dish;
}
}
- Director类中包含了三个Builder类,分别用于构建主食、饮料、配菜。在makeDish()方法中,我们可以使用这些Builder类来逐步构建完整的Dish对象
- 主食Builder类的实现示例
public class DishBuilder {
private String name;
private double price;
private String category;
public DishBuilder setName(String name) {
this.name = name;
return this;
}
public DishBuilder setPrice(double price) {
this.price = price;
return this;
}
public DishBuilder setCategory(String category) {
this.category = category;
return this;
}
public Dish build() {
Dish dish = new Dish();
dish.setName(name);
dish.setPrice(price);
dish.setCategory(category);
return dish;
}
}
- 在Controller层中调用上述类和方法来实现定制菜单功能
@RestController
public class MenuController {
@PostMapping("/menu")
public Dish makeMenu(String mainDish, double mainDishPrice, String drink, double drinkPrice, String sideDish, double sideDishPrice) {
DishBuilder dishBuilder = new DishBuilder().setName(mainDish).setPrice(mainDishPrice).setCategory("主食");
DrinkBuilder drinkBuilder = new DrinkBuilder().setName(drink).setPrice(drinkPrice).setCategory("饮料");
SideDishBuilder sideDishBuilder = new SideDishBuilder().setName(sideDish).setPrice(sideDishPrice).setCategory("配菜");
MenuDirector menuDirector = new MenuDirector(dishBuilder, drinkBuilder, sideDishBuilder);
return menuDirector.makeMenu();
}
}
2. 案例2
- 在后台开发中,对数据库进行复杂的查询是很常见的任务。复杂的查询条件可能由多个参数组成,而且这些参数之间的组合方式也可能会存在多种情况。使用建造者设计模式可以将查询条件拆解成多个Builder类,然后使用一个Director类来协调它们的执行步骤和组合方式,最终得到一个完整的SQL查询语句
- 定义多个Builder类,每个Builder类负责处理一个查询参数:
WhereBuilder用来构建WHERE子句,SelectBuilder用来构建SELECT子句,OrderByBuilder用来构建ORDER BY子句。这里只是示例,实际应用中可能需要根据具体业务需求定义更多的Builder类
public interface IQueryBuilder {
String build(); // 构建完整的查询语句
}
public class WhereBuilder implements IQueryBuilder {
private String whereClause = "";
public WhereBuilder() {
this.whereClause = "WHERE 1=1 ";
}
public WhereBuilder addCondition(String condition) {
this.whereClause += "AND " + condition + " ";
return this;
}
@Override
public String build() {
return this.whereClause;
}
}
public class SelectBuilder implements IQueryBuilder {
private List<String> columns = new ArrayList<>();
public SelectBuilder addColumn(String column) {
this.columns.add(column);
return this;
}
@Override
public String build() {
if (this.columns.size() == 0) {
return "*";
} else {
return StringUtils.join(this.columns.toArray(), ",");
}
}
}
public class OrderByBuilder implements IQueryBuilder {
private String orderBy = "";
public OrderByBuilder() {
this.orderBy = "";
}
public OrderByBuilder addCondition(String orderBy) {
this.orderBy += orderBy;
return this;
}
@Override
public String build() {
if (StringUtils.isBlank(this.orderBy)) {
return "";
} else {
return "ORDER BY " + this.orderBy;
}
}
}
- 定义Director类,该类负责协调各个Builder类的执行步骤和构建顺序
public class QueryBuilder {
private IQueryBuilder whereBuilder;
private IQueryBuilder selectBuilder;
private IQueryBuilder orderByBuilder;
public QueryBuilder(IQueryBuilder whereBuilder, IQueryBuilder selectBuilder, IQueryBuilder orderByBuilder) {
this.whereBuilder = whereBuilder;
this.selectBuilder = selectBuilder;
this.orderByBuilder = orderByBuilder;
}
public String buildQuery() {
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append("SELECT ").append(selectBuilder.build()).append(" FROM employee ");
queryBuilder.append(whereBuilder.build());
queryBuilder.append(orderByBuilder.build());
return queryBuilder.toString();
}
}
- 请求入口,调用QueryBuilder类来构建SQL查询语句
@Controller
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeMapper employeeMapper;
@GetMapping("/list")
public String list(@RequestParam(required = false) String name,
@RequestParam(required = false) Integer departmentId) {
WhereBuilder whereBuilder = new WhereBuilder();
if (StringUtils.isNotBlank(name)) {
whereBuilder.addCondition("name='" + name + "'");
}
if (departmentId != null) {
whereBuilder.addCondition("department_id=" + departmentId);
}
SelectBuilder selectBuilder = new SelectBuilder();
selectBuilder.addColumn("id").addColumn("name").addColumn("department_id");
OrderByBuilder orderByBuilder = new OrderByBuilder();
orderByBuilder.addCondition("id ASC");
QueryBuilder queryBuilder = new QueryBuilder(whereBuilder, selectBuilder, orderByBuilder);
String sql = queryBuilder.buildQuery();
List<Employee> employees = employeeMapper.findBySql(sql);
// ...
return "employee/list";
}
}