設計模式【4】-- 建造者模式詳解
開局一張圖,剩下全靠寫...
<img src="http://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/設計模式.png" style="zoom: 33%;" >
引言
設計模式集合: http://aphysia.cn/categories/...
如果你用過 Mybatis
,相信你對以下代碼的寫法並不陌生,先創建一個 builder
對象,然後再調用 .build()
函數:
InputStream is = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession = sqlSessionFactory.openSession();
上面其實就是我們這篇文章所要講解的 建造者模式 ,下面讓我們一起來琢磨一下它。
什麼是建造者模式
建造者模式是設計模式的一種,將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。(來源於百度百科)
建造者模式,其實是創建型模式的一種,也是23種設計模式中的一種,從上面的定義來看比較模糊,但是不得不承認,當我們有能力用簡潔的話去定義一個東西的時候,我們才是真的瞭解它了,因為這個時候我們已經知道它的界限在哪。
所謂將一個複雜對象的構建與它的表示分離,就是將對象的構建器抽象出來,構造的過程一樣,但是不一樣的構造器可以實現不一樣的表示。
結構與例子
建造者模式主要分為以下四種角色:
Product Bulider ConcreteBuilder Director
説到這裏,可能會有點懵,畢竟全都是定義,下面從實際例子來講講,就拿程序員最喜歡的電腦來説,假設現在要生產多種電腦,電腦有屏幕,鼠標,cpu,主板,磁盤,內存等等,我們可能立馬就能寫出來:
public class Computer { private String screen; private String mouse; private String cpu; private String mainBoard; private String disk; private String memory; ... public String getMouse() { return mouse; } public void setMouse(String mouse) { this.mouse = mouse; } public String getCpu() { return cpu; } public void setCpu(String cpu) { this.cpu = cpu; } ... }
上面的例子中,每一種屬性都使用單獨的 set
方法,要是生產不同的電腦的不同部件,具體的實現還不太一樣,這樣一個類實現起來貌似不是很優雅,比如聯想電腦和華碩電腦的屏幕的構建過程不一樣,而且這些部件的構建,理論上都是電腦的一部分,我們可以考慮 流水線式
的處理。
當然,也有另外一種實現,就是多個構造函數,不同的構造函數帶有不同的參數,實現了可選的參數:
public class Computer { private String screen; private String mouse; private String cpu; private String mainBoard; private String disk; private String memory; public Computer(String screen) { this.screen = screen; } public Computer(String screen, String mouse) { this.screen = screen; this.mouse = mouse; } public Computer(String screen, String mouse, String cpu) { this.screen = screen; this.mouse = mouse; this.cpu = cpu; } ... }
上面多種參數的構造方法,理論上滿足了按需構造的要求,但是還是會有不足的地方:
- 倘若構造每一個部件的過程都比較複雜,那麼構造函數看起來就比較凌亂
- 如果有多種按需構造的要求,構造函數就太多了
- 構造不同的電腦類型,耦合在一塊,必須抽象出來
首先,我們先用流水線的方式,實現按需構造,不能重載那麼多構造函數:
public class Computer { private String screen; private String mouse; private String cpu; private String mainBoard; private String disk; private String memory; public Computer setScreen(String screen) { this.screen = screen; return this; } public Computer setMouse(String mouse) { this.mouse = mouse; return this; } public Computer setCpu(String cpu) { this.cpu = cpu; return this; } public Computer setMainBoard(String mainBoard) { this.mainBoard = mainBoard; return this; } public Computer setDisk(String disk) { this.disk = disk; return this; } public Computer setMemory(String memory) { this.memory = memory; return this; } }
使用的時候,構造起來,就像是流水線一樣,一步一步構造就可以:
Computer computer = new Computer() .setScreen("高清屏幕") .setMouse("羅技鼠標") .setCpu("i7處理器") .setMainBoard("聯想主板") .setMemory("32G內存") .setDisk("512G磁盤");
但是以上的寫法不夠優雅,既然構造過程可能很複雜,為何不用一個特定的類來構造呢?這樣構造的過程和主類就分離了,職責更加清晰,在這裏內部類就可以了:
package designpattern.builder; import javax.swing.*; public class Computer { private String screen; private String mouse; private String cpu; private String mainBoard; private String disk; private String memory; Computer(Builder builder) { this.screen = builder.screen; this.cpu = builder.cpu; this.disk = builder.disk; this.mainBoard = builder.mainBoard; this.memory = builder.memory; this.mouse = builder.mouse; } public static class Builder { private String screen; private String mouse; private String cpu; private String mainBoard; private String disk; private String memory; public Builder setScreen(String screen) { this.screen = screen; return this; } public Builder setMouse(String mouse) { this.mouse = mouse; return this; } public Builder setCpu(String cpu) { this.cpu = cpu; return this; } public Builder setMainBoard(String mainBoard) { this.mainBoard = mainBoard; return this; } public Builder setDisk(String disk) { this.disk = disk; return this; } public Builder setMemory(String memory) { this.memory = memory; return this; } public Computer build() { return new Computer(this); } } }
使用的時候,使用 builder
來構建,構建完成之後,調用build的時候,再將具體的值,賦予我們需要的對象(這裏是 Computer
):
public class Test { public static void main(String[] args) { Computer computer = new Computer.Builder() .setScreen("高清屏幕") .setMouse("羅技鼠標") .setCpu("i7處理器") .setMainBoard("聯想主板") .setMemory("32G內存") .setDisk("512G磁盤") .build(); System.out.println(computer.toString()); } }
但是上面的寫法,如果我們構造多種電腦,每種電腦的配置不太一樣,構建的過程也不一樣,那麼我們就必須將構造器抽象出來,變成一個抽象類。
首先我們定義產品類 Computer
:
public class Computer { private String screen; private String mouse; private String cpu; public void setScreen(String screen) { this.screen = screen; } public void setMouse(String mouse) { this.mouse = mouse; } public void setCpu(String cpu) { this.cpu = cpu; } @Override public String toString() { return "Computer{" + "screen='" + screen + '\'' + ", mouse='" + mouse + '\'' + ", cpu='" + cpu + '\'' + '}'; } }
定義一個抽象的構造類,用於所有的電腦類構造:
public abstract class Builder { abstract Builder buildScreen(String screen); abstract Builder buildMouse(String mouse); abstract Builder buildCpu(String cpu); abstract Computer build(); }
先構造一台聯想電腦,那聯想電腦必須實現自己的構造器,每一款電腦總有自己特殊的地方:
public class LenovoBuilder extends Builder { private Computer computer = new Computer(); @Override Builder buildScreen(String screen) { computer.setScreen(screen); return this; } @Override Builder buildMouse(String mouse) { computer.setMouse(mouse); return this; } @Override Builder buildCpu(String cpu) { computer.setCpu(cpu); return this; } @Override Computer build() { System.out.println("構建中..."); return computer; } }
構建器有了,還需要有個指揮者,它負責去構建我們具體的電腦:
public class Director { Builder builder = null; public Director(Builder builder){ this.builder = builder; } public void doProcess(String screen,String mouse,String cpu){ builder.buildScreen(screen) .buildMouse(mouse) .buildCpu(cpu); } }
使用的時候,我們只需要先構建 builder
,然後把 builder
傳遞給指揮者,他負責具體的構建,構建完之後,構建器調用一下 .build()
方法,就可以創建出一台電腦。
public class Test { public static void main(String[] args) { LenovoBuilder builder = new LenovoBuilder(); Director director = new Director(builder); director.doProcess("聯想屏幕","遊戲鼠標","高性能cpu"); Computer computer = builder.build(); System.out.println(computer); } }
打印結果:
構建中... Computer{screen='聯想屏幕', mouse='遊戲鼠標', cpu='高性能cpu'}
以上其實就是完整的建造者模式,但是我們平時用的,大部分都是自己直接調用構建器 Builder
,一路 set()
,最後 build()
,就創建出了一個對象。
使用場景
構建這模式的好處是什麼?首先想到的應該是將構建的過程解耦了,構建的過程如果很複雜,單獨拎出來寫,清晰簡潔。其次,每個部分的構建,其實都是可以獨立去創建的,不需要多個構造方法,構建的工作交給了構建器,而不是對象本身。專業的人做專業的事。同樣,構建者模式也比較適用於不同的構造方法或者構造順序,可能會產生不同的構造結果的場景。
但是缺點還是有的,需要維護多出來的 Builder
對象,如果多種產品之間的共性不多,那麼抽象的構建器將會失去它該有的作用。如果產品類型很多,那麼定義太多的構建類來實現這種變化,代碼也會變得比較複雜。
最近在公司用 GRPC
,裏面的對象幾乎都是基於構建者模式,鏈式的構建確實寫着很舒服,也比較優雅,代碼是寫給人看的,我們所做的一切設計模式,都是為了拓展,解耦,以及避免代碼只能口口相傳。
【作者簡介】:
秦懷,公眾號【 秦懷雜貨店
】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。個人寫作方向: Java源碼解析
, JDBC
, Mybatis
, Spring
, redis
, 分佈式
, 劍指Offer
, LeetCode
等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花裏胡哨,大多寫系列文章,不能保證我寫的都完全正確,但是我保證所寫的均經過實踐或者查找資料。遺漏或者錯誤之處,還望指正。
- 天才製造者:獨行俠、科技巨頭和AI|深度學習崛起十年
- Go內存管理一文足矣
- React如何原生實現防抖?
- 分佈式日誌存儲架構設計方案
- Chrome插件:雲音樂聽歌識曲
- 全場景AI推理引擎MindSpore Lite, 助力HMS Core視頻編輯服務打造更智能的剪輯體驗
- 頁面搭建系統的那些事兒
- 張文驍:遊戲開發的“零件人”夢碎之後|OneFlow U
- App 出海 —— Google 結算系統面面觀
- Curve 基於 Raft 的寫時延優化
- Pandas 中最常用的 7 個時間戳處理函數
- 實現Nest中參數的聯合類型校驗
- JDK內置鎖深入探究
- Docker 實戰教程之從入門到提高 (八)
- 騰訊三面:Cookie的SameSite瞭解吧,那SameParty呢?
- 實錄 | MegEngine 大 Kernel 卷積工程優化實踐
- 虛幻引擎 5 來了!不止 Lumen、Nanite 新技術,性能及 UI 均迎來大升級
- 前端新手快速上手APICloud App開發
- 高效使用Java構建工具|Maven篇|雲效工程師指北
- 雲音樂隱性關係鏈的探索與實踐