開局一張圖,剩下全靠寫...
<img src="https://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
等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花裡胡哨,大多寫系列文章,不能保證我寫的都完全正確,但是我保證所寫的均經過實踐或者查詢資料。遺漏或者錯誤之處,還望指正。