设计模式第一章(建造者模式)
设计模式第一章(建造者模式)
建造者模式(Builder Pattern)是一种创建型设计模式,将复杂对象的构建过程与其表示分离,使同一构建流程能生成不同形态的对象。 例如:工厂 单列 原型 目的都是造对象。
前言
核心思想
通过拆分构建步骤与对象结构,将复杂对象的创建分为多个独立模块。客户端通过指挥者类间接构建对象,无需直接调用具体构建逻辑,从而隐藏实现细节并提升代码可维护性。
角色分工
-
**建造者(Builder)**:定义构建复杂对象的接口,包含多个抽象方法对应不同构建阶段。
-
**具体建造者(ConcreteBuilder)**:实现建造者接口,负责按步骤组装对象部件。
**指挥者(Director)**:控制构建流程,通过调用建造者方法生成最终对象,不直接暴露构建细节。
优势
- 分离构建与表示:降低代码耦合度,同一构建逻辑可适配不同产品形态。
- 逐步构建:通过分步组装简化复杂对象创建流程。
- 封装性:客户端仅需指定对象类型和参数,无需了解内部构建细节。
- 灵活性:修改构建逻辑时无需改动客户端代码,便于维护和扩展。
代码部分
- 第一版本 ~ 最终的版本
- 每个版本的需求变化已经不足之处
- 如果优化每个版本的缺陷
- 最终写一个应用的场景
- 核心思想-链式调用
V1
- 需求背景
- 我们现在有一个普通的javabean对象,需求为,如果年龄大小于10并且名字为 tom那么我们就提示不能创建;
- 如果年龄大于10,并且名字叫 Jerry 的话,也不能创建
java 代码部分
public class UserV1 {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public boolean check() {if (age < 10 && "Tom".equals(name)) {return false;}if (age > 10 && "Jerry".equals(name)) {return false;}return true;}public static void main(String[] args) {// 10 岁不能叫 tom// 大于 10岁 jetty 也不行UserV1 userV1 = new UserV1();userV1.setAge(1);userV1.setName("Tom");//传统判断 调用时机不对,不调用都有可能// 有没有一种方案是 必须是设置了属性后再校验, 因为属性可能需要上下文if (userV1.check()) {}}
缺陷部分
我们看到上面的代码缺陷部分,调用时机不对,如果调用者不使用,那么就不能校验了,而且这个校验需要有上下文的时机,必须是属性都设置了后再调用
V2
我们针对上面的缺点进行改进,使用一个内部的builder 对象构建
代码部分
public class User {private String name;private int age;private User() {}public static Builder builder() {return new Builder();}//创建一个内部类public static class Builder {private String name;private int age;private Builder() {}public User build() throws IllegalAccessException {User user = new User();user.name = this.name;user.age = this.age;if (age < 10 && "Tom".equals(name)) {throw new IllegalAccessException("年龄小于10不能叫Tom");}if (age > 10 && "Jerry".equals(name)) {throw new IllegalAccessException("年龄大于10不能叫Jerry");}return user;}public Builder name(String name) {this.name = name;return this;}public Builder age(int age) {this.age = age;return this;}}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
使用部分
我们通过User.builder()方法得到一个用户对象,在最红调用build的时候,我们进行校验,这样就保证了上下文的顺序
public class TestV2Main {public static void main(String[] args) throws IllegalAccessException {// 缺陷 user创建之后还可以修改里面的属性,实际上是一次性的对象User user = User.builder().name("Tom").age(11).build();user.setAge(222);}
}
缺陷部分
当我们build完后这个对象,我们还是可以通过set更改里面的属性,实际上这个对象是一次性的,当我们设置后,就不能够进行更改。类似于我们发起的 http对象
V3
对外提供的builde方法,在创建之后就不让修改
代码部分
public class User {private final String name;private final int age;private User(Builder builder) {this.name = builder.name;this.age = builder.age;}public static Builder builder() {return new Builder();}//创建一个内部类public static class Builder {private String name;private int age;private Builder() {}public User build() throws IllegalAccessException {// 可以判断 必须设置值User user = new User(this);if (age < 10 && "Tom".equals(name)) {throw new IllegalAccessException("年龄小于10不能叫Tom");}if (age > 10 && "Jerry".equals(name)) {throw new IllegalAccessException("年龄大于10不能叫Jerry");}return user;}public Builder name(String name) {this.name = name;return this;}public Builder age(int age) {this.age = age;return this;}}public String getName() {return name;}public int getAge() {return age;}}
使用部分
public static void main(String[] args) throws IllegalAccessException {// dslUser user = User.builder().name("Tom").age(11).build();user.setAge()//会报错,没有该方法// 案例演示List.of(1,2,3,4).stream().map(String::valueOf).filter(x -> x.length() > 1).collect(Collectors.toList());}
总结
该部分代码的演变为,增加一个私有构造方法,使之外面不能直接new,然后去掉set方法,只保留get方法,在 builder 里面进行复制,内部类的使用方式
使用案例
我们根据传入的参数,构建一个 sql 的查询和修改的类,使之做到 dsl
v1
我们定义了枚举,SELECT,DELETE,UPDATE 采用 Switch 实现,记住一点,如果我们一开始想不出来使用设计模式,那么我们就按照最基本的方式,先把功能实现
代码部分
public class SqlWarp {private SqlWarp () {}public static SqlBuilder builder(SqlType sqlType) {return new SqlBuilder(sqlType);}public static class SqlBuilder {final SqlType sqlType;// 查询的列private String[] columns;//查询的表名private String tableNm;// 查询的where 条件private String where;private Map<String,String> setMap = new LinkedHashMap<>();private SqlBuilder(SqlType sqlType) {this.sqlType = sqlType;}public SqlBuilder select(String ...columns) {this.columns = columns;return this;}public SqlBuilder table(String tableNm) {this.tableNm = tableNm;return this;}public SqlBuilder where(String where) {this.where = where;return this;}public SqlBuilder set(String column,String columnValue) {setMap.put(column,columnValue);return this;}public String buildSql() {StringBuilder sql = new StringBuilder();switch (sqlType) {case SELECT -> {sql.append(" SELECT ").append(String.join(",",columns)).append(" FROM ").append(tableNm);if (where != null) {sql.append(" WHERE ").append(where);}}case UPDATE -> {sql.append(" UPDATE ").append(tableNm).append(" SET ");sql.append(setMap.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining(",")));if (where != null) {sql.append(" WHERE ").append(where);}}case INSERT -> {}case DELETE -> {}default -> throw new UnsupportedOperationException("暂不支持该类型的参数");}return sql.toString();}}enum SqlType {SELECT,UPDATE,DELETE,INSERT,}}
使用部分
public class SqlTestMain {public static void main(String[] args) {// 问题点,如果我是 select 我还是可以调用set方法,那么有没有一种功能,限制调用的apiString sql = SqlWarp.builder(SqlWarp.SqlType.SELECT).select("name", "age", "address").table("user").where(" age = 1").buildSql();System.out.println(sql);String updSql = SqlWarp.builder(SqlWarp.SqlType.UPDATE).table("t_user_inf").set("name", "'张三'").set("age", "10").set("address", "'武汉市洪山区'").where(" id = 2").buildSql();System.out.println(updSql);}
}
缺陷部分
当我们使用查询(select)方法的时候,我们也可以调用 set方法,实际上,该方法是给update 使用的,那么有没有一种方式是,如果是 select 我只有我需要的那部分 api 其他的不对外开放,那么下一部分就是优化该部分
String sql = SqlWarp.builder(SqlWarp.SqlType.SELECT).select("name", "age", "address").set("aa","111").table("user").where(" age = 1").buildSql();
v2
优化查询不能调用 set 方法
代码部分
public class SQL {private SQL() {}public static SelectBuilder select(String ...column) {return new SelectBuilder(column);}public static UpdateBuilder update() {return new UpdateBuilder();}/*** 查询 builder*/static class SelectBuilder {// 要查询的列private final String[] columns;//所属表private String table;//where 条件private String where;private SelectBuilder(String[] columns) {this.columns = columns;}public SelectBuilder from(String table) {this.table = table;return this;}public SelectBuilder where(String where) {this.where = where;return this;}public String buildSql() {StringBuilder sql = new StringBuilder();sql.append(" SELECT ").append(String.join(",",columns)).append(" FROM ").append(table);if (where != null) {sql.append(" WHERE ").append(where);}return sql.toString();}}//更新public static class UpdateBuilder {// 设置列的条件private Map<String,String> setMap = new LinkedHashMap<>();//表名private String table;//where 条件private String where;private UpdateBuilder() {}public UpdateBuilder table(String table) {this.table = table;return this;}public UpdateBuilder set(String key,String value) {setMap.put(key,value);return this;}public UpdateBuilder where(String where) {this.where = where;return this;}public String buildSql() {StringBuilder sql = new StringBuilder();sql.append(" UPDATE ").append(table).append(" SET ").append(setMap.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining(",")));if (where != null) {sql.append(" WHERE ").append(where);}return sql.toString();}}}
使用部分
public class SQLTest {public static void main(String[] args) {//查询String sql = SQL.select("name", "age", "address").from("t_user_inf").where("name = 'zhangsan'").buildSql();System.out.println("查询sql:"+sql);String updSql = SQL.update().table("t_user_inf").set("name", "zhangsan").set("age", "10").where("name = 'lisi'").buildSql();System.out.println("更新sql:"+updSql);}
缺陷部分
虽然我们限制了 查询不能使用set方法,但是,如果我们可以同时设置多个 from 方法,多个where 方法,不能称之为一次性对象,下一节使用接口实现,最终版的 dsl 方式DSL(领域特定语言)
V3
- 查询实现
- 修改的实现
- 删除的实现
查询dsl
- 查询需要设置的值,定义一套接口
代码部分
public class SqlSelect {private SqlSelect() {}public static TableStage select() {return new SelectBuilder();}static class SelectBuilder implements TableStage,ColumnStage,WhereStage {// 要查询的列private String[] columns;// 要查询的表private String table;// where 条件private String where;@Overridepublic WhereStage column(String... columns) {this.columns = columns;return this;}@Overridepublic ColumnStage from(String table) {this.table = table;return this;}@Overridepublic WhereStage where(String where) {this.where = where;return this;}@Overridepublic String buildSql() {StringBuilder sql = new StringBuilder();sql.append(" SELECT ").append(String.join(",",columns)).append(" FROM ").append(table);if (where != null) {sql.append(" WHERE ").append(where);}return sql.toString();}}// table 策略interface TableStage {ColumnStage from(String table);}// 查询的列interface ColumnStage {WhereStage column(String ...column);}// where 条件interface WhereStage {WhereStage where(String where);String buildSql();}}
使用部分
public class TestMain {public static void main(String[] args) {String updSql = SqlSelect.select().from("t_user_inf").column("name", "age", "address").where(" id = 6").buildSql();}
}
修改dsl
- 接口部分
- 接口实现
- 调用部分
接口部分
public class SQLStrategy {// 查询 table , update table set aa whereinterface TableStrategy {SetStrategy table(String tableName);}// 设置列 stageinterface SetStrategy {SetStrategy set(String name,String value);WhereStrategy where(String where);}// where条件interface WhereStrategy {String buildSql();}}
接口实现
public class SQL {private SQL() {}public static SQLStrategy.TableStrategy update() {return new UpdateBuilder();}public static SqlDelete.TableStrategy delete() {return new SqlDelete.SqlDeleteBuilder();}static class UpdateBuilder implements SQLStrategy.TableStrategy, SQLStrategy.SetStrategy, SQLStrategy.WhereStrategy {private String[] columns;private Map<String,String> setMap = new LinkedHashMap<>();private String where;private String table;@Overridepublic SQLStrategy.SetStrategy table(String table) {this.table = table;return this;}@Overridepublic SQLStrategy.SetStrategy set(String name, String value) {setMap.put(name,value);return this;}@Overridepublic SQLStrategy.WhereStrategy where(String where) {this.where = where;return this;}@Overridepublic String buildSql() {StringBuilder sql = new StringBuilder();sql.append(" UPDATE ").append(table).append(" SET ");sql.append(setMap.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining(",")));if (where != null) {sql.append(" WHERE ").append(where);}return sql.toString();}}
}
使用部分
String sql = SQL.update().table("t_user_inf").set("name", "'张三'").set("age", "12").where("where id = 15").buildSql();System.out.println(sql);
删除dsl
- 蜜封属性
代码部分
public class SqlDelete {private SqlDelete() {}public static TableStrategy delete() {return new SqlDeleteBuilder();}public static class SqlDeleteBuilder implements TableStrategy,WhereStrategy {private String table;private String where;@Overridepublic WhereStrategy table(String tableName) {this.table = tableName;return this;}@Overridepublic WhereStrategy where(String where) {this.where = where;return this;}@Overridepublic String buildSql() {StringBuilder sql = new StringBuilder();sql.append(" delete from ").append(table).append(" WHERE ").append(where);return sql.toString();}}// 查询 table , update table set aa whereinterface TableStrategy {WhereStrategy table(String tableName);}// where条件interface WhereStrategy {WhereStrategy where(String where);String buildSql();}}
使用部分
String delSql = SQL.delete().table("t_user_inf").where(" name = '张三'").buildSql();System.out.println(delSql);