建造者模式属于创建型模式,目标是为了创建一个复杂的对象。具体的思路是:将一个复杂对象的创建过程和需要构建的部件或者数据进行分离,由一个建造者对象Builder来获取创建对象的部件或者数据,然后让一个指导者Director对象来指导创建过程,最终创建出需要的产品。
构建者模式的使用场景大多是需要构建的对象依赖很多外部数据,尤其是使用同一类的数据的数量还不能确定的情况下,用单纯的bean对象来保存数据的方式就会显得很臃肿,在这种场景下,使用构建者模式就能很好的解决问题。比如我们在写sql语句的时候,select关键字和where关键字后面的条件大多数时候需要用户来指定,数量和逻辑是无法确定的,虽然查询的select语句的格式是固定的,但后面的参数不能确定,需要由调用方在使用的时候指定,在这种场景中,我们就可以将构建的框架select XXX from XXX where XXX的整体骨架和里面的参数XXX进行分离,在创建的过程中就会显得优雅很多,也具有良好的扩展性,后面我们会简单实现这个场景的示例代码。
在java语言中,构建者模式往往和链式编程绑定在一起,所谓的链式编程,就是将原本一个bean的setter方法进行改造,让setter方法返回对象本身,这样在使用的时候就可以不用重新指定bean的名称,而是直接在一个setter方法后面接着写另一个setter方法。比如下面的代码,我们假定一个User类,需要设置它的属性,使用链式编程和不使用链式编程的方法如下:
- 不使用链式编程的写法:
/**
* 不适用链式编程的bean定义和使用(main方法中)
*/
public class User {
private String name;
private int age;
private String gender;
private String address;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setAddress(String address) {
this.address = address;
}
public static void main(String[] args) {
User user = new User();
user.setName("aaa");
user.setAge(18);
user.setGender("男");
user.setAddress("中国");
}
}
- 使用链式编程的写法
/**
* 适用链式编程的bean定义和使用(main方法中)
*/
public class User {
private String name;
private int age;
private String gender;
private String address;
/**
* setter方法不再是返回void,而是返回对象本身
*/
public User setName(String name) {
this.name = name;
return this;
}
public User setAge(int age) {
this.age = age;
return this;
}
public User setGender(String gender) {
this.gender = gender;
return this;
}
public User setAddress(String address) {
this.address = address;
return this;
}
public static void main(String[] args) {
User user = new User();
user.setName("aaa")
.setAge(18)
.setGender("男")
.setAddress("中国");
}
}
建造者模式类图和说明
- Builder :生成器的接口,定义创建对象的部件的方法
- ConcreteBuilderA 和ConcreteBuilderB:构造器模式的具体实现类
- Director :指导者,主要用来使用Builder接口获取到的部件或者数据,以一个统一的过程来构建所需的Product对象。
生成Select的sql场景示例
我们需要构建一个select语句,我们都知道,查询的SQL语言的结构是select XXX from XXX where XXX,但是里面的参数是使用端来确定的,这个时候我们就可以使用构建者模式,将创建的结构和参数部件进行分离。从上面的结构图我们知道,构建者模式需要一个获取部件的Builder接口,和指导创建过程的Director两个角色,我们这里将这两个角色进行统一放到一个SqlBuilder中,其中的build()方法就完成了指导创建过程的职能,这样简化以后,我们在使用的时候写完参数,就直接可以使用build()方法创建出sql语句,而不需要再创建一个Director类了。具体代码如下:
1. 创建SqlBuilder接口
public interface SqlBuilder {
/**
* 添加select 后面的查询字段
*
* @param fieldName :
* @return :
*/
SqlBuilder addSelectField(String fieldName);
/**
* 表名称
* @param tableName :
* @return :
*/
SqlBuilder tableName(String tableName);
/**
* where查询条件
*
* @return :
*/
SqlBuilder where();
/**
* and条件
*
* @return :
*/
SqlBuilder and();
/**
* or 条件
*
* @return :
*/
SqlBuilder or();
/**
* 相等的条件过滤
*
* @param fieldName :
* @param value :
* @return :
*/
SqlBuilder equalsFilter(String fieldName, String value);
/**
* like条件过滤
*
* @param fieldName :
* @param value :
* @return :
*/
SqlBuilder likeFilter(String fieldName, String value);
/**
* in条件过滤
*
* @param fieldName :
* @param values :
* @return :
*/
SqlBuilder inFilter(String fieldName, List<String> values);
String build();
}
2. 写一个具体的实现类ConcreteSqlBuilder
在实现类中,我们用两个StringBuilder来承接sql拼接的参数
/**
* builder的具体实现
*/
public class ConcreteSqlBuilder implements SqlBuilder {
private final StringBuilder selectFields = new StringBuilder();
private String tableName;
private final StringBuilder whereFilter = new StringBuilder();
@Override
public SqlBuilder addSelectField(String fieldName) {
if (selectFields.length() == 0) {
selectFields.append(" ").append(fieldName);
} else {
selectFields.append(" ,").append(fieldName);
}
return this;
}
@Override
public SqlBuilder tableName(String tableName) {
this.tableName = tableName;
return this;
}
@Override
public SqlBuilder where() {
// where后面的单独放在一个StringBuilder中,不需要添加where字符串
return this;
}
@Override
public SqlBuilder and() {
if (whereFilter.length() != 0) {
whereFilter.append(" and ");
}
return this;
}
@Override
public SqlBuilder or() {
if (whereFilter.length() != 0) {
whereFilter.append(" or ");
}
return this;
}
@Override
public SqlBuilder equalsFilter(String fieldName, String value) {
whereFilter.append(fieldName).append(" = '").append(value).append("'");
return this;
}
@Override
public SqlBuilder likeFilter(String fieldName, String value) {
whereFilter.append(fieldName).append(" like '%").append(value).append("%'");
return this;
}
@Override
public SqlBuilder inFilter(String fieldName, List<String> values) {
whereFilter.append(fieldName).append(" in (");
for (int i = 0; i < values.size(); i++) {
if (i != 0) {
whereFilter.append(",");
}
whereFilter.append("'").append(values.get(i)).append("'");
}
whereFilter.append(")");
return null;
}
@Override
public String build() {
//具体构建的过程
StringBuilder sqlBuilder = new StringBuilder("select ");
sqlBuilder.append(selectFields.toString()).append(" from ").append(tableName);
if (whereFilter.length() == 0) {
return sqlBuilder.toString();
}
sqlBuilder.append(" where ").append(whereFilter.toString());
return sqlBuilder.toString();
}
}
3. 写一个测试方法,展示其使用方法
public class Client {
@Test
public void testNoWhereSql(){
SqlBuilder sqlBuilder = new ConcreteSqlBuilder();
sqlBuilder.addSelectField("user_name")
.addSelectField("sex")
.addSelectField("address")
.addSelectField("phone_number")
.tableName("user");
String sql = sqlBuilder.build();
System.out.println("--------------no where sql -----------------");
System.out.println(sql);
}
@Test
public void testHasWhereSql(){
SqlBuilder sqlBuilder = new ConcreteSqlBuilder();
sqlBuilder.addSelectField("user_name")
.addSelectField("sex")
.addSelectField("address")
.addSelectField("phone_number")
.tableName("user")
// where
.where()
.equalsFilter("sex","gender")
.and()
.likeFilter("address","aaa")
.or()
.inFilter("user_name", new ArrayList<>(Arrays.asList("aaa","bbbb","cccc")));
String sql = sqlBuilder.build();
System.out.println("--------------has where sql -----------------");
System.out.println(sql);
}
}
测试结果如下:
--------------no where sql -----------------
select user_name ,sex ,address ,phone_number from user
--------------has where sql -----------------
select user_name ,sex ,address ,phone_number from user where sex = 'gender' and address like '%aaa%' or user_name in ('aaa','bbbb','cccc')
从上面的代码示例中,我们可以看到,利用构建者模式和java的链式编程,将原本杂乱的sql拼接过程,以我们思维想对应的方式展现在我们的面前,在使用的时候,我们就可以用我们平常写sql的方式来拼接sql,最终得到我们想要的结果,整个拼接过程就会优雅很多。
后记
个人总结,欢迎转载、评论、批评指正