建造者模式
建造者模式(Builder Pattern):將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。建造者模式是一種對象建立型模式。java
建造者模式一步一步建立一個複雜的對象,它容許用戶只經過指定複雜對象的類型和內容就能夠構建它們,用戶不須要知道內部的具體構建細節。sql
角色
Builder(抽象建造者):它爲建立一個產品Product對象的各個部件指定抽象接口,在該接口中通常聲明兩類方法,一類方法是buildPartX(),它們用於建立複雜對象的各個部件;另外一類方法是getResult(),它們用於返回複雜對象。Builder既能夠是抽象類,也能夠是接口。數據庫
ConcreteBuilder(具體建造者):它實現了Builder接口,實現各個部件的具體構造和裝配方法,定義並明確它所建立的複雜對象,也能夠提供一個方法返回建立好的複雜產品對象。apache
Product(產品角色):它是被構建的複雜對象,包含多個組成部件,具體建造者建立該產品的內部表示並定義它的裝配過程。編程
Director(指揮者):指揮者又稱爲導演類,它負責安排複雜對象的建造次序,指揮者與抽象建造者之間存在關聯關係,能夠在其construct()建造方法中調用建造者對象的部件構造與裝配方法,完成複雜對象的建造。客戶端通常只須要與指揮者進行交互,在客戶端肯定具體建造者的類型,並實例化具體建造者對象(也能夠經過配置文件和反射機制),而後經過指揮者類的構造函數或者Setter方法將該對象傳入指揮者類中。設計模式
在建造者模式的定義中提到了複雜對象,那麼什麼是複雜對象?簡單來講,複雜對象是指那些包含多個成員屬性的對象,這些成員屬性也稱爲部件或零件,如汽車包括方向盤、發動機、輪胎等部件,電子郵件包括髮件人、收件人、主題、內容、附件等部件緩存
示例
產品角色 Computer
安全
public class Computer {
private String brand;
private String cpu;
private String mainBoard;
private String hardDisk;
private String displayCard;
private String power;
private String memory;
// 省略 getter, setter, toString
}
抽象建造者 builder
bash
public abstract class Builder {
protected Computer computer = new Computer();
public abstract void buildBrand();
public abstract void buildCPU();
public abstract void buildMainBoard();
public abstract void buildHardDisk();
public abstract void buildDisplayCard();
public abstract void buildPower();
public abstract void buildMemory();
public Computer createComputer() {
return computer;
}
}
具體建造者 DellComputerBuilder
,ASUSComputerBuilder
,分別建造戴爾電腦和華碩電腦微信
public class DellComputerBuilder extends Builder {
@Override
public void buildBrand() {
computer.setBrand("戴爾電腦");
}
@Override
public void buildCPU() {
computer.setCpu("i5-8300H 四核");
}
@Override
public void buildMainBoard() {
computer.setMainBoard("戴爾主板");
}
@Override
public void buildHardDisk() {
computer.setHardDisk("1T + 128GB SSD");
}
@Override
public void buildDisplayCard() {
computer.setDisplayCard("GTX1060 獨立6GB");
}
@Override
public void buildPower() {
computer.setPower("4芯 鋰離子電池 180W AC適配器");
}
@Override
public void buildMemory() {
computer.setMemory("4G + 4G");
}
}
public class ASUSComputerBuilder extends Builder{
@Override
public void buildBrand() {
computer.setBrand("華碩電腦");
}
@Override
public void buildCPU() {
computer.setCpu("Intel 第8代 酷睿");
}
@Override
public void buildMainBoard() {
computer.setMainBoard("華碩主板");
}
@Override
public void buildHardDisk() {
computer.setHardDisk("256GB SSD");
}
@Override
public void buildDisplayCard() {
computer.setDisplayCard("MX150 獨立2GB");
}
@Override
public void buildPower() {
computer.setPower("3芯 鋰離子電池 65W AC適配器");
}
@Override
public void buildMemory() {
computer.setMemory("1 x SO-DIMM 8GB");
}
}
指揮者 ComputerDirector
,指揮構建過程
public class ComputerDirector {
public Computer construct(Builder builder) {
// 逐步構建複雜產品對象
Computer computer;
builder.buildBrand();
builder.buildCPU();
builder.buildDisplayCard();
builder.buildHardDisk();
builder.buildMainBoard();
builder.buildMemory();
builder.buildPower();
computer = builder.createComputer();
return computer;
}
}
客戶端測試
public class Test {
public static void main(String[] args) {
ComputerDirector director = new ComputerDirector();
Builder asusBuilder = new ASUSComputerBuilder();
Computer asusComputer = director.construct(asusBuilder);
System.out.println(asusComputer.toString());
Builder dellBuilder = new DellComputerBuilder();
Computer dellComputer = director.construct(dellBuilder);
System.out.println(dellComputer.toString());
}
}
輸出
Computer{brand='華碩電腦', cpu='Intel 第8代 酷睿', mainBoard='華碩主板', hardDisk='256GB SSD', displayCard='MX150 獨立2GB', power='3芯 鋰離子電池 65W AC適配器', memory='1 x SO-DIMM 8GB'}
Computer{brand='戴爾電腦', cpu='i5-8300H 四核', mainBoard='戴爾主板', hardDisk='1T + 128GB SSD', displayCard='GTX1060 獨立6GB', power='4芯 鋰離子電池 180W AC適配器', memory='4G + 4G'}
能夠經過反射機制和配置文件配合,建立具體建造者對象
public class Test {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
ComputerDirector director = new ComputerDirector();
// 從數據庫或者配置文件中讀取具體建造者類名
Class c = Class.forName("com.designpattern.ASUSComputerBuilder");
Builder asusBuilder = (Builder) c.newInstance();
Computer asusComputer = director.construct(asusBuilder);
System.out.println(asusComputer.toString());
}
}
建造者模式總結
建造者模式的主要優勢以下:
在建造者模式中,客戶端沒必要知道產品內部組成的細節,將產品自己與產品的建立過程解耦,使得相同的建立過程能夠建立不一樣的產品對象。
每個具體建造者都相對獨立,而與其餘的具體建造者無關,所以能夠很方便地替換具體建造者或增長新的具體建造者,用戶使用不一樣的具體建造者便可獲得不一樣的產品對象。因爲指揮者類針對抽象建造者編程,增長新的具體建造者無須修改原有類庫的代碼,系統擴展方便,符合 "開閉原則"。
能夠更加精細地控制產品的建立過程。將複雜產品的建立步驟分解在不一樣的方法中,使得建立過程更加清晰,也更方便使用程序來控制建立過程。
建造者模式的主要缺點以下:
建造者模式所建立的產品通常具備較多的共同點,其組成部分類似,若是產品之間的差別性很大,例如不少組成部分都不相同,不適合使用建造者模式,所以其使用範圍受到必定的限制。
若是產品的內部變化複雜,可能會致使須要定義不少具體建造者類來實現這種變化,致使系統變得很龐大,增長系統的理解難度和運行成本。
適用場景:
須要生成的產品對象有複雜的內部結構,這些產品對象一般包含多個成員屬性。
須要生成的產品對象的屬性相互依賴,須要指定其生成順序。
對象的建立過程獨立於建立該對象的類。在建造者模式中經過引入了指揮者類,將建立過程封裝在指揮者類中,而不在建造者類和客戶類中。
隔離複雜對象的建立和使用,並使得相同的建立過程能夠建立不一樣的產品。
建造者模式的典型應用和源碼分析
java.lang.StringBuilder 中的建造者模式
StringBuilder
的繼承實現關係以下所示
Appendable
接口以下
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
中的 append
方法使用了建造者模式,不過裝配方法只有一個,並不算複雜,append
方法返回的是 StringBuilder
自身
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence {
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
// ...省略...
}
StringBuilder
的父類 AbstractStringBuilder
實現了 Appendable
接口
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));
}
}
// ...省略...
}
咱們能夠看出,Appendable
爲抽象建造者,定義了建造方法,StringBuilder
既充當指揮者角色,又充當產品角色,又充當具體建造者,建造方法的實現由 AbstractStringBuilder
完成,而 StringBuilder
繼承了 AbstractStringBuilder
java.lang.StringBuffer 中的建造者模式
StringBuffer 繼承與實現關係以下
這分明就與 StringBuilder
同樣嘛!
那它們有什麼不一樣呢?
public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence {
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
//...省略...
}
看 StringBuffer
的源碼如上,它們的區別就是: StringBuffer
中的 append
加了 synchronized
關鍵字,因此StringBuffer
是線程安全的,而 StringBuilder
是非線程安全的
StringBuffer
中的建造者模式與 StringBuilder
是一致的
Google Guava 中的建造者模式
ImmutableSet
不可變Set的主要方法以下
ImmutableSet
類中 of
, copyOf
等方法返回的是一個 ImmutableSet
對象,這裏是一個建造者模式,所構建的複雜產品對象爲 ImmutableSet
ImmutableSet
的內部類 ImmutableSet.Builder
以下所示
public static class Builder<E> extends ArrayBasedBuilder<E> {
@CanIgnoreReturnValue
public ImmutableSet.Builder<E> add(E... elements) {
super.add(elements);
return this;
}
@CanIgnoreReturnValue
public ImmutableSet.Builder<E> addAll(Iterator<? extends E> elements) {
super.addAll(elements);
return this;
}
public ImmutableSet<E> build() {
ImmutableSet<E> result = ImmutableSet.construct(this.size, this.contents);
this.size = result.size();
return result;
}
//...省略...
}
其中的 add
、addAll
等方法返回的是 ImmutableSet.Builder
對象自己,而 build
則返回 ImmutableSet
對象,因此 ImmutableSet.Builder
是具體建造者,add
、addAll
等方法則至關於buildPartX()
,是裝配過程當中的一部分,build
方法則是 getResult()
,返回最終建立好的複雜產品對象
ImmutableSet 使用示例以下:
public class Test2 {
public static void main(String[] args) {
Set<String> set = ImmutableSet.<String>builder().add("a").add("a").add("b").build();
System.out.println(set);
// [a, b]
}
}
再來看一個,通常建立一個 guava緩存
的寫法以下所示
final static Cache<Integer, String> cache = CacheBuilder.newBuilder()
//設置cache的初始大小爲10,要合理設置該值
.initialCapacity(10)
//設置併發數爲5,即同一時間最多隻能有5個線程往cache執行寫入操做
.concurrencyLevel(5)
//設置cache中的數據在寫入以後的存活時間爲10秒
.expireAfterWrite(10, TimeUnit.SECONDS)
//構建cache實例
.build();
這裏很明顯,咱們不用看源碼就能夠知道這裏是一個典型的建造者模式,CacheBuilder.newBuilder()
建立了一個具體建造者,.initialCapacity(10)
、.concurrencyLevel(5)
、.expireAfterWrite(10, TimeUnit.SECONDS)
則是構建過程,最終的 .build()
返回建立完成的複雜產品對象
看看源碼是否是符合咱們的猜想
public final class CacheBuilder<K, V> {
// 建立一個具體建造者
public static CacheBuilder<Object, Object> newBuilder() {
return new CacheBuilder();
}
// 建造過程之一
public CacheBuilder<K, V> initialCapacity(int initialCapacity) {
Preconditions.checkState(this.initialCapacity == -1, "initial capacity was already set to %s", this.initialCapacity);
Preconditions.checkArgument(initialCapacity >= 0);
this.initialCapacity = initialCapacity;
return this;
}
// 建造過程之一
public CacheBuilder<K, V> concurrencyLevel(int concurrencyLevel) {
Preconditions.checkState(this.concurrencyLevel == -1, "concurrency level was already set to %s", this.concurrencyLevel);
Preconditions.checkArgument(concurrencyLevel > 0);
this.concurrencyLevel = concurrencyLevel;
return this;
}
// 建造過程之一
public CacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) {
Preconditions.checkState(this.expireAfterWriteNanos == -1L, "expireAfterWrite was already set to %s ns", this.expireAfterWriteNanos);
Preconditions.checkArgument(duration >= 0L, "duration cannot be negative: %s %s", duration, unit);
this.expireAfterWriteNanos = unit.toNanos(duration);
return this;
}
// 建造完成,返回建立完的複雜產品對象
public <K1 extends K, V1 extends V> Cache<K1, V1> build() {
this.checkWeightWithWeigher();
this.checkNonLoadingCache();
return new LocalManualCache(this);
}
// ...省略...
}
很明顯符合咱們的猜想,initialCapacity()
、concurrencyLevel()
、expireAfterWrite()
等方法對傳進來的參數進行處理和設置,返回 CacheBuilder
對象自己,build
則把 CacheBuilder
對象 做爲參數,new 了一個 LocalManualCache
對象返回
mybatis 中的建造者模式
咱們來看 org.apache.ibatis.session
包下的 SqlSessionFactoryBuilder
類
裏邊不少重載的 build
方法,返回值都是 SqlSessionFactory
,除了最後兩個全部的 build
最後都調用下面這個 build
方法
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException var13) {
;
}
}
return var5;
}
其中最重要的是 XMLConfigBuilder
的 parse
方法,代碼以下
public class XMLConfigBuilder extends BaseBuilder {
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
private void parseConfiguration(XNode root) {
try {
Properties settings = this.settingsAsPropertiess(root.evalNode("settings"));
this.propertiesElement(root.evalNode("properties"));
this.loadCustomVfs(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
// ...省略...
}
parse
方法最終要返回一個 Configuration
對象,構建 Configuration
對象的建造過程都在 parseConfiguration
方法中,這也就是 Mybatis
解析 XML配置文件
來構建 Configuration
對象的主要過程
因此 XMLConfigBuilder
是建造者 SqlSessionFactoryBuilder
中的建造者,複雜產品對象分別是 SqlSessionFactory
和 Configuration
後記
點擊[閱讀原文]可訪問個人我的博客:http://laijianfeng.org
關注【小旋鋒】微信公衆號,及時接收博文推送
參考:
劉偉:設計模式Java版
慕課網java設計模式精講 Debug 方式+內存分析
本文分享自微信公衆號 - 小旋鋒(whirlysBigData)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。