設計模式 | 建造者模式及典型應用
本文目錄
建造者模式
建造者模式(Builder Pattern):將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。建造者模式是一種物件建立型模式。
建造者模式一步一步建立一個複雜的物件,它允許使用者只通過指定複雜物件的型別和內容就可以構建它們,使用者不需要知道內部的具體構建細節。
角色
Builder(抽象建造者):它為建立一個產品Product物件的各個部件指定抽象介面,在該介面中一般宣告兩類方法,一類方法是buildPartX(),它們用於建立複雜物件的各個部件;另一類方法是getResult(),它們用於返回複雜物件。Builder既可以是抽象類,也可以是介面。
ConcreteBuilder(具體建造者):它實現了Builder介面,實現各個部件的具體構造和裝配方法,定義並明確它所建立的複雜物件,也可以提供一個方法返回建立好的複雜產品物件。
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
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
參考:
劉偉:設計模式Java版
慕課網java設計模式精講 Debug 方式+記憶體分析
更多內容請訪問我的個人部落格:http://laijianfeng.org/
開啟微信掃一掃,關注【小旋鋒】微信公眾號,及時接收博文推送