一、介绍
建造者模式,简单的说,就是将对象的属性与创建分离,使得同样的构建过程可以创建不同的对象。
初次接触这个概念,可能有点闷逼,下面我们来举例,你就好懂了!
假设有一个对象里面有20
个属性,如果我要使用这个对象,我们可能需要把这20个属性弄明白,然后在构造函数或者创建一个对象通过set
一个一个去指定,显然这对开发者来说非常吃力!
- 属性1
- 属性2
- ......
- 属性19
- 属性20
通常一个对象中,重要的属性只有几个,如果我们要使用这个对象时,只需要指定一些比较重要的属性,其他属性由一个中间类来帮我们完成复杂的创建过程,是不是会轻松很多?这就是对象的属性与创建分离!也就是我们所要介绍的建造者设计模式。
说了这么多,感觉还是有些理论,我们看一下具体的例子。
二、示例
我们都知道,台式机由很多零部件组成,但是不同的用途可能所需的零部件不一样,比如用途:游戏电脑,可能对cpu、显卡要求很高;办公电脑,可能就没有那么高要求,不太需要配置声卡;家庭影音电脑,要求也不是很高,但是各个配件都要齐全等等。
我们就以组装电脑为例,来看一下怎么实现对象的属性与创建分离!
- 首先,我们创建一个实体类,定义好属性,内容如下:
/**
* 实体类
* 电脑
*/
public class Computer {
/**CPU*/
private String cpu;
/**显卡*/
private String graphicsCard;
/**硬盘*/
private String hardDisk;
/**内存*/
private String memory;
/**主板*/
private String mainBoard;
/**声卡*/
private String soundCard;
//... 省略 setter 和 getter
@Override
public String toString() {
return "Computer [cpu = " cpu
",graphicsCard = " graphicsCard
",hardDisk = " hardDisk
",memory = " memory
",mainBoard = " mainBoard
",soundCard = " soundCard "]";
}
}
- 再创建一个建造类,也就是负责组装电脑,内容如下:
/**
* 建造者类
*/
public class ComputerBuilder {
/**用途*/
private String type;
/**是否配置声卡,默认是*/
private boolean isSoundCard = true;
/**额外增加的内存条(单位G)*/
private Integer addMemory;
public String getType() {
return type;
}
public ComputerBuilder type(String type) {
this.type = type;
return this;
}
public boolean isSoundCard() {
return isSoundCard;
}
public ComputerBuilder setSoundCard(boolean soundCard) {
isSoundCard = soundCard;
return this;
}
public Integer getAddMemory() {
return addMemory;
}
public ComputerBuilder addMemory(Integer addMemory) {
this.addMemory = addMemory;
return this;
}
/**创建Computer对象*/
public Computer builder(){
return new Computer(this);
}
}
- 其中一个关键就是
builder()
方法,里面使用Computer
类的构造方法来创建对象,因此我们在Computer
类中重载一个构造方法,内容如下:
/**
* 实体类
* 电脑
*/
public class Computer {
/**CPU*/
private String cpu;
/**显卡*/
private String graphicsCard;
/**硬盘*/
private String hardDisk;
/**内存*/
private String memory;
/**主板*/
private String mainBoard;
/**声卡*/
private String soundCard;
... 省略 setter 和 getter
public Computer(ComputerBuilder builder) {
//按照用途不同,进行配置
if("游戏配置".equals(builder.getType())){
this.cpu = "Intel i7 处理器";
this.graphicsCard = "英伟达显卡";
this.hardDisk = "500G 机械硬盘";
this.mainBoard = "华硕 主板";
}else if("办公配置".equals(builder.getType())){
this.cpu = "Intel i5 处理器";
this.graphicsCard = "英伟达显卡";
this.hardDisk = "200G 固态硬盘";
this.mainBoard = "华硕 主板";
}else if("家庭影音配置".equals(builder.getType())){
this.cpu = "Intel i3 处理器";
this.graphicsCard = "英伟达显卡";
this.hardDisk = "500G 机械硬盘";
this.mainBoard = "华硕 主板";
}
//判断是否需要配置声卡
if(builder.isSoundCard()){
this.soundCard = "英特尔声卡";
}else{
this.soundCard = "不配置声卡";
}
//默认4G 内存
this.memory = (4 builder.getAddMemory()) "g 内存";
}
@Override
public String toString() {
return "Computer [cpu = " cpu
",graphicsCard = " graphicsCard
",hardDisk = " hardDisk
",memory = " memory
",mainBoard = " mainBoard
",soundCard = " soundCard "]";
}
}
- 最后,编写一个客户端,测试一下,内容如下:
public class BuilderClient {
public static void main(String[] args) {
//创建一个办公配置的组装电脑
Computer computer = new ComputerBuilder()
.type("办公配置")//指定配置
.addMemory(4)//增加4G 内存
.setSoundCard(false)//不配置声卡
.builder();
//打印结果
System.out.println(computer.toString());
}
}
输出结果:
代码语言:javascript复制Computer [cpu = Intel i5 处理器,graphicsCard = 英伟达显卡,hardDisk = 200G 固态硬盘,memory = 8g 内存,mainBoard = 华硕 主板,soundCard = 不配置声卡]
如果我们想要使用游戏配置
,只需在type()
方法里传入游戏配置,即可帮我们创建相应对口的组装电脑,使用者不需要关心具体创建过程,只需要指定关键属性类型就可以了,从而实现对象的属性与构建的分离!
三、应用
建造者设计模式,使用非常广泛,尤其是在开源框架中,比如我们最熟悉的 Mybatis 框架,获取SqlSessionFactory
就是一个建造者模式的场景实际应用。
在使用 Mybatis (不使用 Spring 框架进行整合)的时候,我们会这样打开一个SqlSessionFactory
,内容如下:
// MyBatis配置文件路径
String resources = "mybatis_config.xml";
// 获取一个输入流
Reader reader = Resources.getResourceAsReader(resources);
// 获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 打开一个会话
SqlSession sqlSession = sqlSessionFactory.openSession();
// 具体操作
...
我们继续来看看SqlSessionFactoryBuilder
类的源码,内容如下:
public class SqlSessionFactoryBuilder {
...
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
.....
/**最后通过Configuration 创建一个SqlSessionFactory */
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
实际的Configuration
类的属性非常多,部分内容如下:
/**
* @author Clinton Begin
*/
public class Configuration {
protected Environment environment;
protected boolean safeRowBoundsEnabled = false;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase = false;
protected boolean aggressiveLazyLoading = true;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys = false;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls = false;
protected String logPrefix;
protected Class <? extends Log> logImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
protected Integer defaultStatementTimeout;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
.....
}
很明显,Mybatis
通过配置文件获取的配置属性,解析完数据源配置信息之后,通过SqlSessionFactoryBuilder
创建一个SqlSessionFactory
对象,因为数据源配置特别多,不少于40个,这么多的配置,如果让开发者在代码中一个一个的去set
,估计都要疯狂了,改用配置文件形式去配置属性,工作量就会少很多!
四、总结
建造者模式不像一些设计模式有比较固定或者比较类似的实现方式,它的核心只是分离对象属性与创建,整个实现比较自由。
总的来说,建造者模式的实现大致有两种写法:
- 第一种:通过建造者直接 new 一个对象,用获取的对象然后进行属性赋值
- 第二种:建造者通过构造方法创建一个对象,在构造方法里进行属性赋值
总体而言,两种没有太大的优劣之分,在合适的场景下选择合适的写法就好了。
但是,建造者模式这种设计模式,优缺点比较明显。从优点来说:
- 客户端不必知道产品内部细节,将产品本身与产品创建过程解耦,使得相同的创建过程可以创建不同的产品对象,建造者独立,易扩展;
- 可以更加精细地控制产品的创建过程,将复杂对象分门别类抽出不同的类别来,便于控制细节风险;
建造者模式说不上缺点,只能说这种设计模式的使用比较受限:
- 产品属性之间差异很大且属性没有默认值可以指定,这种情况是没法使用建造者模式的,我们可以试想,一个对象20个属性,彼此之间毫无关联且每个都需要手动指定,那么很显然,即使使用了建造者模式也是毫无作用;
在IT这个行业,复杂的需求、复杂的业务逻辑层出不穷,合理分析场景,在合适的场景下使用建造者模式,一定会起到事半功倍的效果!
五、参考
博客园-五月的仓颉-建造者模式