在Apache Flink的源碼中,有很多對(duì)象都是通過類的一個(gè)靜態(tài)成員類Builder來創(chuàng)建的,例如org.apache.flink.api.common.operators.ResourceSpec攒菠、org.apache.flink.runtime.checkpoint.OperatorSubtaskState等盅视,這種方式有什么好處创橄,為什么不采用直接new的方式創(chuàng)建對(duì)象射亏,在《Effective Java》第三版“ITEM 2: CONSIDER A BUILDER WHEN FACED WITH MANY CONSTRUCTOR PARAMETERS”章節(jié)中可以找到答案:
In summary, the Builder pattern is a good choice when designing classes whose constructors or static factories would have more than a handful of parameters, especially if many of the parameters are optional or of identical type. Client code is much easier to read and write with builders than with telescoping constructors, and builders are much safer than JavaBeans.
可以從上面這段話總結(jié)出如下幾點(diǎn):
- 構(gòu)造方法有多個(gè)參數(shù)的時(shí)候推薦使用Builder模式
- Builder模式相比telescoping constructors客戶端的代碼更易于閱讀和編寫
- Builder模式相比JavaBeans更加安全
本文希望幫助讀者能夠理解如何使用Builder來創(chuàng)建對(duì)象以及這種方式的優(yōu)缺點(diǎn),在實(shí)際開發(fā)過程中能夠善于使用田晚。
Builder模式的使用
以ResourceSpec為例嘱兼,下面代碼只是抽取出必要的部分進(jìn)行說明
public final class ResourceSpec implements Serializable {
private final CPUResource cpuCores;
private final MemorySize taskHeapMemory;
private final MemorySize taskOffHeapMemory;
private final MemorySize managedMemory;
private final Map<String, ExternalResource> extendedResources;
private ResourceSpec(
final CPUResource cpuCores,
final MemorySize taskHeapMemory,
final MemorySize taskOffHeapMemory,
final MemorySize managedMemory,
final Map<String, ExternalResource> extendedResources) {
checkNotNull(cpuCores);
this.cpuCores = cpuCores;
this.taskHeapMemory = checkNotNull(taskHeapMemory);
this.taskOffHeapMemory = checkNotNull(taskOffHeapMemory);
this.managedMemory = checkNotNull(managedMemory);
this.extendedResources =
checkNotNull(extendedResources).entrySet().stream()
.filter(entry -> !checkNotNull(entry.getValue()).isZero())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
/** Creates a new ResourceSpec with all fields unknown. */
private ResourceSpec() {
this.cpuCores = null;
this.taskHeapMemory = null;
this.taskOffHeapMemory = null;
this.managedMemory = null;
this.extendedResources = new HashMap<>();
}
public Map<String, ExternalResource> getExtendedResources() {
throwUnsupportedOperationExceptionIfUnknown();
return Collections.unmodifiableMap(extendedResources);
}
// ------------------------------------------------------------------------
// builder
// ------------------------------------------------------------------------
public static Builder newBuilder(double cpuCores, int taskHeapMemoryMB) {
return new Builder(new CPUResource(cpuCores), MemorySize.ofMebiBytes(taskHeapMemoryMB));
}
public static Builder newBuilder(double cpuCores, MemorySize taskHeapMemory) {
return new Builder(new CPUResource(cpuCores), taskHeapMemory);
}
public static class Builder {
private CPUResource cpuCores;
private MemorySize taskHeapMemory;
private MemorySize taskOffHeapMemory = MemorySize.ZERO;
private MemorySize managedMemory = MemorySize.ZERO;
private Map<String, ExternalResource> extendedResources = new HashMap<>();
private Builder(CPUResource cpuCores, MemorySize taskHeapMemory) {
this.cpuCores = cpuCores;
this.taskHeapMemory = taskHeapMemory;
}
public Builder setCpuCores(double cpuCores) {
this.cpuCores = new CPUResource(cpuCores);
return this;
}
public Builder setTaskHeapMemory(MemorySize taskHeapMemory) {
this.taskHeapMemory = taskHeapMemory;
return this;
}
public Builder setTaskHeapMemoryMB(int taskHeapMemoryMB) {
this.taskHeapMemory = MemorySize.ofMebiBytes(taskHeapMemoryMB);
return this;
}
public Builder setTaskOffHeapMemory(MemorySize taskOffHeapMemory) {
this.taskOffHeapMemory = taskOffHeapMemory;
return this;
}
public Builder setTaskOffHeapMemoryMB(int taskOffHeapMemoryMB) {
this.taskOffHeapMemory = MemorySize.ofMebiBytes(taskOffHeapMemoryMB);
return this;
}
public Builder setManagedMemory(MemorySize managedMemory) {
this.managedMemory = managedMemory;
return this;
}
public Builder setManagedMemoryMB(int managedMemoryMB) {
this.managedMemory = MemorySize.ofMebiBytes(managedMemoryMB);
return this;
}
public Builder setExtendedResource(ExternalResource extendedResource) {
this.extendedResources.put(extendedResource.getName(), extendedResource);
return this;
}
public ResourceSpec build() {
checkArgument(cpuCores.getValue().compareTo(BigDecimal.ZERO) > 0);
checkArgument(taskHeapMemory.compareTo(MemorySize.ZERO) > 0);
return new ResourceSpec(
cpuCores, taskHeapMemory, taskOffHeapMemory, managedMemory, extendedResources);
}
}
}
通過上面的代碼可以知道,使用Builder模式需要如下的步驟
- 類的構(gòu)造方法聲明為private
- 成員變量聲明為private final
- 類里有一個(gè)靜態(tài)成員類Builder
- 類里有個(gè)靜態(tài)方法生成Builder對(duì)象贤徒,例如上例中的newBuilder方法
- Builder的構(gòu)造方法也聲明為private
- Builder與所在的類擁有相同的成員變量
- Builder的構(gòu)造方法中的參數(shù)芹壕,可以認(rèn)為是必填參數(shù),其他可以認(rèn)為是可選參數(shù)接奈,所以其他成員變量都必須有默認(rèn)值
- Builder里面每個(gè)setter方法返回值都是Builder
- Builder里面有個(gè)build()方法用來生成具體的ResourceSpec對(duì)象
客戶端調(diào)用
看下org.apache.flink.api.common.operators.util.SlotSharingGroupUtils里面是如何生成ResourceSpec對(duì)象的踢涌,直接調(diào)用ResourceSpec.newBuilder方法設(shè)置必填參數(shù),然后繼續(xù)使用各種setter方法設(shè)置可選參數(shù)序宦,最后調(diào)用build()方法生成ResourceSpec對(duì)象睁壁。
public class SlotSharingGroupUtils {
public static ResourceSpec extractResourceSpec(SlotSharingGroup slotSharingGroup) {
if (!slotSharingGroup.getCpuCores().isPresent()) {
return ResourceSpec.UNKNOWN;
}
Preconditions.checkState(slotSharingGroup.getCpuCores().isPresent());
Preconditions.checkState(slotSharingGroup.getTaskHeapMemory().isPresent());
Preconditions.checkState(slotSharingGroup.getTaskOffHeapMemory().isPresent());
Preconditions.checkState(slotSharingGroup.getManagedMemory().isPresent());
return ResourceSpec.newBuilder(
slotSharingGroup.getCpuCores().get(),
slotSharingGroup.getTaskHeapMemory().get())
.setTaskOffHeapMemory(slotSharingGroup.getTaskOffHeapMemory().get())
.setManagedMemory(slotSharingGroup.getManagedMemory().get())
.setExtendedResources(
slotSharingGroup.getExternalResources().entrySet().stream()
.map(
entry ->
new ExternalResource(
entry.getKey(), entry.getValue()))
.collect(Collectors.toList()))
.build();
}
}
對(duì)比telescoping constructors
所謂的“telescoping constructors”可以翻譯成重疊構(gòu)造方法,例如flink中DistributedCacheEntry這個(gè)類的構(gòu)造方法
public static class DistributedCacheEntry implements Serializable {
public String filePath;
public Boolean isExecutable;
public boolean isZipped;
public byte[] blobKey;
/** Client-side constructor used by the API for initial registration. */
public DistributedCacheEntry(String filePath, Boolean isExecutable) {
this(filePath, isExecutable, null);
}
/** Client-side constructor used during job-submission for zipped directory. */
public DistributedCacheEntry(String filePath, boolean isExecutable, boolean isZipped) {
this(filePath, isExecutable, null, isZipped);
}
/** Server-side constructor used during job-submission for files. */
public DistributedCacheEntry(String filePath, Boolean isExecutable, byte[] blobKey) {
this(filePath, isExecutable, blobKey, false);
}
/** Server-side constructor used during job-submission for zipped directories. */
public DistributedCacheEntry(
String filePath, Boolean isExecutable, byte[] blobKey, boolean isZipped) {
this.filePath = filePath;
this.isExecutable = isExecutable;
this.blobKey = blobKey;
this.isZipped = isZipped;
}
}
可以看到,第一個(gè)構(gòu)造方法只有2個(gè)必填參數(shù)潘明,第二個(gè)構(gòu)造方法在第一個(gè)的基礎(chǔ)上增加了一個(gè)可選參數(shù)行剂,第三個(gè)造方法在第一個(gè)的基礎(chǔ)上增加了另一個(gè)可選參數(shù),第四個(gè)構(gòu)造方法包括了所有的參數(shù)钳降。這種方式就是所謂的“telescoping constructors”厚宰。這種方式的弊端就在于如果參數(shù)過多會(huì)導(dǎo)致可讀性降低,并且如果參數(shù)都為相同類型遂填,new對(duì)象的時(shí)候铲觉,會(huì)非常容易混淆參數(shù)的順序,導(dǎo)致運(yùn)行時(shí)錯(cuò)誤吓坚。
對(duì)比JavaBeans
JavaBeans的方式其實(shí)就是類中的成員變量都有g(shù)etter和setter方法备燃,這樣就不能將成員變量聲明為final,在調(diào)用setter方法的時(shí)候凌唬,需要考慮線程安全問題。
總結(jié)
在開發(fā)過程中需要根據(jù)實(shí)際的情況選擇使用哪種方式創(chuàng)建對(duì)象更加的合適漏麦,Builder模式也有一些不足之處客税,例如代碼會(huì)更加冗長(zhǎng)。
注意:本文所說的“Builder模式”并不是設(shè)計(jì)模式中所謂的“Builder Pattern”撕贞,希望讀者不要混淆更耻。以上所有源碼來自于Apache Flink 1.14.0版本。