建造者模式(Builder)——從組裝電腦開始
建造者模式概括起來就是將不同獨立的組件按照一定的條件組合起來構成一個相對業務完整的對象。調用者無需知道構造的過程。
我們從組裝電腦開始
讓我們從買組裝電腦開始吧。
首先要買一個電腦,一般都有兩個選擇 —— 品牌電腦和組裝電腦,一般人為了省事和放心都會選擇買品牌電腦(也就是整機)。在這里,為了更好的分析問題,假定我們為了性價比決定要買組裝電腦。那么我們該怎么做呢。
首先我們得學習一個完整的電腦的組成部分有哪些?
經過翻查一部分資料發現,主要部件分為主板、CPU、顯卡、顯示屏、內存條等。這還剛開始,我們光知道這個還不行,每個硬件的品牌少說都有好幾種,我們肯定希望在價格允許的情況選最好的。所以我們還得花時間找資料了解每個部件對應的品牌的口碑與使用者的實際體驗情況(比如淘寶的賣家秀,以及網上一些專業的測評人的報告等)。
好了,研究完這個之后呢,總算是可以決定怎么搭配各種硬件了。但是最后又有一個問題來了,這些硬件買回來了,我們這些小白不會裝啊,都不知道主機箱里每個部件對應的位置是哪里,以及怎么裝上去。萬一裝壞了怎么辦,那錢豈不是白花了。
又是一番曲折之后,總算是把整個電腦組裝完畢了。就在我們舉杯同慶的時候,突然發現了一個嚴重的問題……
“天吶,還開不了機啊,還要給電腦裝系統啊”
“啊!!!那怎么辦啊,都忙活一整天了。”
“還能怎么辦啊?買都買了,裝都裝好了,只能裝系統呀”
“也是,可我不會裝系統啊,你會么?”
“……嗯~我也不會”
“哎,咱們還是去查資料怎么裝系統吧”
上面的故事雖然在我們ITer來看,顯得很搞笑和誇張。但是對於電腦小白來說,這可是絕對會發生的。因為在大學,我就是這么過來的。光怎么重裝系統都花了我一天時間。
但是,別急,我們隔壁寢室的一個同學他早上說他也要買組裝電腦。我們現在去看他怎么弄的吧,說實話,我既希望他也跟我們一樣經過種種折磨,但是又希望他也能一帆風順,這樣就能教我們怎么裝系統了啊。
什么?已經開始玩電腦了
當我們進寢室門的時候,令我們目瞪口呆的事情發生了,我發現他已經開始玩英雄聯盟了,都已經三殺並成功結束游戲。
我馬上就問他怎么這么快就玩上電腦了,然后我就把我一整天的遭遇發泄了出來。只見他哈哈大笑
”現在誰還自己買各種部件來裝電腦啊,更何況像你們還不懂這些。“
”那不然怎么弄,品牌機同等價位的比組裝機要貴好多啊“
”哈哈,你這個蠢嘛批,你可以去電腦城讓老板幫你組裝不就行了,你只需要為此付一些手工費就行了嘛,也不貴啊“
”……“
開始對號入座
第一則故事其實就相當於我們沒有用建造者模式開發可能面臨的一些問題。為了生成一個業務對象(組裝電腦),我們得花很多時間精力來收集業務對象的成員信息(組成部分)。這么多對象全由我一個人(客戶端)組織,這樣就會強耦合,並很有可能因為一些細小需求的改變而導致整個功能異常(忘記裝系統,內存條型號不對等)。從而浪費了更多的時間和精力,增加了我們的勞動成本和經濟成本。
第二則故事就完全不同,我(用戶/客戶端)完全不需關心業務對象的構建過程,只需要找電腦城老板(構建者)要對象就行了。
首先有一個前提,就是有一個規則依據(契約)來構造一個正確的業務對象(電腦)。
所以為了以正確姿勢來組裝電腦,我們定義了一些必要的成員(硬件)
public interface IFullComputer {
string Mainboard { get; }
string CPU { get; }
string Disk { get; }
string Graphics { get; }
string Display { get; }
bool HasOperatingSystem { get; }
}
有了它才代表一個完整正確的電腦,我們先來看我們第二個故事是怎么實現結果調用的吧。
public class Client {
// 交給電腦城老板
private void IWantBuyComputer() {
// 見到老板
var boss = new ComputerCityBoss();
// 告訴老板我想要什么配置的電腦,這里簡單起就用老板推薦的
var computerBuilder = new DefaultFullComputerBuilder();
var computer = boss.TellMeThenReturnComputer(computerBuilder);
Console.WriteLine("電腦組件完畢,是否預裝系統:" + computer.HasOperatingSystem);
}
}
客戶端(用戶)已經很簡單了,就跟我們現在很多人買電腦一樣,去電腦城把自己搭配的電腦配置給老板,然后就等着老板把組裝好的電腦交給你。你根本不需要知道電腦組裝的細節。這樣從代碼上就能做客戶端與業務數據分離。
現在我們來看具體實現代碼。
public interface IFullComputerBuilder : IFullComputer {
IFullComputer Create();
}
public class DefaultFullComputerBuilder : AbstractFullComputerBuilder {
protected override void SetCPU() {
}
protected override void SetDisk() {
}
protected override void SetDisplay() {
}
protected override void SetGraphics() {
}
protected override void SetMainboard() {
}
}
// 老板與品牌商有合作
public abstract class AbstractFullComputerBuilder : IFullComputerBuilder {
public string Mainboard { get; set; } = "默認品牌主板";
public string CPU { get; set; } = "默認品牌CPU";
public string Disk { get; set; } = "默認品牌內存";
public string Graphics { get; set; } = "默認品牌顯卡";
public string Display { get; set; } = "默認品牌顯示器";
public bool HasOperatingSystem { get; set; }
public IFullComputer Create() {
SetMainboard();
SetCPU();
SetDisk();
SetDisplay();
SetGraphics();
InstallOperatingSystem();
if (!HasOperatingSystem) throw new InvalidOperationException("install faild: no operating system");
return this;
}
protected abstract void SetMainboard();
protected abstract void SetCPU();
protected abstract void SetDisk();
protected abstract void SetGraphics();
protected abstract void SetDisplay();
private void InstallOperatingSystem() {
//if (!condition) return;
HasOperatingSystem = true;
}
}
老板就會根據你的要求來給你組裝電腦。當然,如果你沒有特殊要求,那老板就會默認用品牌合作商的,利潤更多嘛。
public class ComputerCityBoss {
public IFullComputer TellMeThenReturnComputer(IFullComputerBuilder builder) {
return builder.Create();
}
}
再來看.NETCore源碼加深學習
我們在可以通過一些優秀的框架的源碼來學習加深我們對知識點的理解。
在 .netcore 中,Builder 模式是很常見的。相信很多人都知道 .netcore 啟動程序就是用 Builder 模式:
public class Program {
public static void Main(string[] args) {
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => {
webBuilder.UseStartup<Startup>();
});
}
其中 IHostBuilder 就是一個構造者。我們來看默認實現者 HostBuilder 的源代碼:
public IHost Build()
{
if (_hostBuilt)
{
throw new InvalidOperationException("Build can only be called once.");
}
_hostBuilt = true;
BuildHostConfiguration();
CreateHostingEnvironment();
CreateHostBuilderContext();
BuildAppConfiguration();
CreateServiceProvider();
return _appServices.GetRequiredService<IHost>();
}
很明顯,它所做的就跟我們之前講的例子 —— AbstractFullComputerBuilder 做的是一樣的。都了為了構成一個完整的對象,內部組織了很多的模塊。而外部客戶端(Program)根本不需要知道其內部的具體細節,只負責調用 Builder 即可。