Factory还是Builder

每次看到框架中的工厂模式时,便会想起一个和它功能相近的模式——创建者模式。二者均属于创建型设计模式,都在帮助使用者更好地完成创建对象的工作。那么穿过浅层的印象,略微深入。它们为什么作为两种模式出现?二者有什么区别?围绕着两个问题,笔者将简要分享自己的拙见。
工厂模式示例
单一职责思想告诉我们,一个代码块应该专注于一件事情,如果某一类对象的创建流程较为复杂,并不只是简单地new()
一下就可以,那么可以考虑使用工厂模式来解决。
以SpringFramework中BeanFactory创建Bean对象的工厂方法为例:
protected Object createBean(String beanName, BeanDefinition beanDefinition,Object[] args) throws BeansException {
Object bean = null;
try {
bean = resolveBeforeInstantiation(beanName, beanDefinition);
if (null != bean) {
return bean;
}
bean = createBeanInstance(beanDefinition, beanName, args);
// 给 Bean 填充属性
applyPropertyValues(beanName, bean, beanDefinition);
// 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法
bean = initializeBean(beanName, bean, beanDefinition);
} catch (Exception e) {
throw new BeansException("Instantiation of bean failed", e);
}
registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);
addSingleton(beanName, bean);
return bean;
}
如上述代码所示,在SpringFramework中bean对象的创建就是一个相较于new()
来讲复杂得多的场景。需要考虑对bean定义的修改,属性填充、初始化等等操作。
建造者模式示例
建造者模式针对的问题:
- 在对象创建时,必要的参数较多,由构造函数传入会较为臃肿。
- 成员变量之间有一定约束关系,比如三角形的两边之和大于第三边。或者说是依赖关系。
- 如果希望成员变量是不可变的,那么其set方法自然不能暴漏在外。
- 避免对象出现中间状态或者说无效状态,保证对象创建的原子性。在下文的示例中,如果使用set方式初始化,那么在全部参数被填充之前,对象是不可用的。这一现象在多线程中会导致未初始化的对象被用户获取的问题。
以构建一个三角形作为示例。
package com.caldarius.study.dp.builder;
public class Triangle {
private int sideALength;
private String sideAName;
private int sideBLength;
private String sideBName;
private int sideCLength;
private String sideCName;
private Triangle(Builder builder) {
this.sideALength = builder.sideALength;
this.sideAName = builder.sideAName;
this.sideBLength = builder.sideBLength;
this.sideBName = builder.sideBName;
this.sideCLength = builder.sideCLength;
this.sideCName = builder.sideCName;
}
@Override
public String toString() {
return "Triangle{" +
"sideALength=" + sideALength +
", sideAName='" + sideAName + '\'' +
", sideBLength=" + sideBLength +
", sideBName='" + sideBName + '\'' +
", sideCLength=" + sideCLength +
", sideCName='" + sideCName + '\'' +
'}';
}
public static class Builder{
private int sideALength;
private String sideAName;
private int sideBLength;
private String sideBName;
private int sideCLength;
private String sideCName;
public Triangle build(){
//名字不能为null,不能为""
if (sideAName == null || sideAName.isEmpty() || sideBName == null || sideBName.isEmpty() || sideCName == null || sideCName.isEmpty()) {
throw new IllegalArgumentException(getSideNameLog());
}
//边长大于0
if (sideALength <= 0 || sideBLength <=0 || sideCLength <=0) {
throw new IllegalArgumentException(getSideLengthLog());
}
//任意两边之和大于第三边
if (sideALength + sideBLength <= sideCLength) {
throw new IllegalArgumentException(getSideLengthLog());
}
if (sideALength + sideCLength <= sideBLength) {
throw new IllegalArgumentException(getSideLengthLog());
}
if (sideBLength + sideCLength <= sideALength) {
throw new IllegalArgumentException(getSideLengthLog());
}
return new Triangle(this);
}
//
public Builder setSideALength(int sideALength) {
if (sideALength <= 0) {
throw new IllegalArgumentException("边长不能小于等于0"+sideALength);
}
this.sideALength = sideALength;
//链式编程 -> 返回自身
return this;
}
public Builder setSideAName(String sideAName) {
if (sideAName == null || sideAName.isEmpty()) {
throw new IllegalArgumentException("名字不能为null,不能为\"\""+sideAName);
}
this.sideAName = sideAName;
return this;
}
public Builder setSideBLength(int sideBLength) {
if (sideBLength <= 0) {
throw new IllegalArgumentException("边长不能小于等于0" + sideBLength);
}
this.sideBLength = sideBLength;
return this;
}
public Builder setSideBName(String sideBName) {
if (sideBName == null || sideAName.isEmpty()) {
throw new IllegalArgumentException("名字不能为null,不能为\"\""+sideBName);
}
this.sideBName = sideBName;
return this;
}
public Builder setSideCLength(int sideCLength) {
if (sideCLength <= 0) {
throw new IllegalArgumentException("边长不能小于等于0"+sideCLength);
}
this.sideCLength = sideCLength;
return this;
}
public Builder setSideCName(String sideCName) {
if (sideCName == null || sideCName.isEmpty()) {
throw new IllegalArgumentException("名字不能为null,不能为\"\""+sideCName);
}
this.sideCName = sideCName;
return this;
}
private String getSideLengthLog() {
return "sideALength=" + sideALength +
", sideCLength=" + sideCLength +
", sideBLength=" + sideBLength;
}
private String getSideNameLog() {
return "sideAName=" + sideAName +
", sideCName=" + sideCName +
", sideBName=" + sideBName;
}
}
public static void main(String[] args) {
Triangle triangle = new Builder()
.setSideALength(3)
.setSideAName("a")
.setSideBLength(4)
.setSideBName("b")
.setSideCLength(5)
.setSideCName("C").build();
System.out.println(triangle);
Triangle triangle2 = new Builder()
.setSideALength(1)
.setSideAName("a")
.setSideBLength(2)
.setSideBName("b")
.setSideCLength(3)
.setSideCName("C").build();
}
}
/*
output:
Triangle{sideALength=3, sideAName='a', sideBLength=4, sideBName='b', sideCLength=5, sideCName='C'}
Exception in thread "main" java.lang.IllegalArgumentException: sideALength=1, sideCLength=3, sideBLength=2
*/
区别与联系
从上文的代码示例可以直观感觉到,相对于工厂模式创建不同但是相关类型的对象,这里的复杂度我认为主要是创建什么类型的对象,以及需要进行什么样的处理。
建造者模式更多聚焦于某一种类型创建的复杂度,主要体现在初始化的参数上。
二者之间的区别是工厂更多关注创建的流程,建造者更多关注具体的细节。
网上有一个经典的例子很好地解释了两者的区别。
顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨。
建议从要解决的问题出发,灵活选择模式或者创造自己的模式。