设计模式 建造者模式

words: 934    views:    time: 4min

有时,一个对象会由不同的部件组成,一些情况下,在某些部件没有恰当的值之前,对象不能作为一个完整的产品使用,甚至有时候,一个对象的组成部件必须按照某个顺序赋值才有意义。建造者模式利用导演者对象以及具体建造者对象,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

1. 结构

  • 抽象建造者角色(Builder):给出各个部件的创建接口,以规范Product各个组成部件的建造,通常独立于应用的业务逻辑。
  • 具体建造者角色(ConcreteBuilder):实现各个部件的具体构造和装配,与应用程序紧密相关。
  • 导演者角色(Director):负责安排具体建造者创建。
  • 产品角色(Product):建造的对象。

导演者将客户端创建产品的请求划分为对各个部件的建造请求,并将这些请求委派给具体建造者,客户端直接访问导演者而对建造过程不感知。它将产品本身与产品的创建过程分开,使得相同的创建过程可以创建不同的产品对象。因为每一个具体建造者都相对独立,因此可以很方便地替换或增加具体建造者。这样在有些情况下,如果将具体建造者再抽象出一层接口,就可以很灵活的改变建造过程了。

2. 应用

2.1. java StringBuilder

StringBuilder中的append方法就使用了建造者模式,不过装配方法只有一个,append返回自身,比较简单。

Appendable为抽象建造者,定义了建造方法

java.lang.Appendable
1
2
3
4
5
public interface Appendable {
Appendable append(CharSequence csq) throws IOException;
Appendable append(CharSequence csq, int start, int end) throws IOException;
Appendable append(char c) throws IOException;
}

StringBuilder既充当导演者,也充当产品角色,还充当具体建造者,具体建造在AbstractStringBuilder中实现

java.lang.AbstractStringBuilder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;

public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}

private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}

// ...
}

另外,StringBuilder还有一个兄弟实现类StringBuffer,它们的区别只在于是否使用了同步

java.lang.StringBuilder
1
2
3
4
5
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
java.lang.StringBuffer
1
2
3
4
5
6
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}

2.2. mybatis SqlSessionFactoryBuilder

org.apache.ibatis.session.SqlSessionFactoryBuilder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.
}
}
}

public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

//...
}

从命名可以看出SqlSessionFactoryBuilder想要构建一个SqlSessionFactory,不过首先它需要先构建一个Configuration,而ConfigurationXMLConfigBuilder负责构建

org.apache.ibatis.builder.xml.XMLConfigBuilder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class XMLConfigBuilder extends BaseBuilder {

public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

//...
}

parse方法最终返回一个Configuration,构建Configuration对象的建造过程都在parseConfiguration方法中,这也就是 Mybatis解析XML配置文件的主要过程了。


参考:

  1. 《java与模式》