開局一張圖,剩下全靠寫...

引言
如果你用過 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
等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花里胡哨,大多寫系列文章,不能保證我寫的都完全正確,但是我保證所寫的均經過實踐或者查找資料。遺漏或者錯誤之處,還望指正。