介紹
定義: 將一個複雜的對象與他的表示分離,使同樣的構建過程可以創建不同的表示
主要作用: 在用戶不知道創建過程和細節的情況下就可以直接創建複雜的對象。
何時使用: 一些基本部件不會變,而其組合經常變化的時候。
如何解決: 將變與不變分離開。
關鍵代碼: 建造者:創建和提供實例,導演:管理建造出來的實例的依賴關係。
應用實例: 1、去肯德基,漢堡、可樂、薯條、炸雞翅等是不變的,而其組合是經常變化的,生成出所謂的"套餐"。 2、JAVA 中的 StringBuilder。
優點:
- 產品的建造和表示分離,實現瞭解耦 ,可以在用戶不知道創建過程和細節的情況下就可以直接創建複雜的對象。
- 將複雜產品的創建過程分解在不同方法中,使創建過程更清晰。
- 具體的構建者類之間相互獨立,有利於系統的擴展。增加新的具體建造者無需修改之前代碼,符合開閉原則。
缺點: 1、產品必須有共同點,範圍有限制。 2、如內部變化複雜,會有很多的建造類。
使用場景: 1、需要生成的對象具有複雜的內部結構。 2、需要生成的對象內部屬性本身相互依賴。
注意事項: 與工廠模式的區別是:建造者模式更加關注與零件裝配的順序。(工廠製造零件,建造者負責按順序把零件組裝。)
舉例
我們用造房子爲例,說明建造者模式。
造房子的大概步驟分爲四步:地基–鋼筋–鋪電線–粉刷。無論最終房子是平房還是瓦房,樓房還是別墅,都是這四部。也就是同樣的構建過程可以創建不同的表示。
但是實際上,我們不會自己造房子,我們會首先要找建築公司或者包工頭(Director指揮者),包工頭按照圖紙(抽象的Builder)去指揮工人(具體的Builder)蓋房子(具體的Product)。也就是在用戶不知道創建過程和細節的情況下就可以直接創建複雜的對象。
首先我們需要一個抽象的Builder,也就是圖紙
//抽象的建造者:定義方法和接口
public abstract class Builder {
abstract void buildA();//地基
abstract void buildB();//鋼筋
abstract void buildC();//鋪電線
abstract void buildD();//粉刷
abstract Product getProduct();//完工:得到產品
}
還需要最後的產品房子,房子帶有四個屬性
//具體的產品:房子
public class Product {
private String buildA;//地基
private String buildB;//鋼筋
private String buildC;//電線
private String buildD;//粉刷
@Override
public String toString() {
return "product{" +
"buildA='" + buildA + '\'' +
", buildB='" + buildB + '\'' +
", buildC='" + buildC + '\'' +
", buildD='" + buildD + '\'' +
'}';
}
public String getBuildA() {
return buildA;
}
public void setBuildA(String buildA) {
this.buildA = buildA;
}
public String getBuildB() {
return buildB;
}
public void setBuildB(String buildB) {
this.buildB = buildB;
}
public String getBuildC() {
return buildC;
}
public void setBuildC(String buildC) {
this.buildC = buildC;
}
public String getBuildD() {
return buildD;
}
public void setBuildD(String buildD) {
this.buildD = buildD;
}
}
抽象的Builder(圖紙)需要由具體的Builder(工人)去實現,工人實現後就製造出了產品:房子
//具體的建造者:工人
public class Work extends Builder{
private Product product;//最後製造的房子
public Work() {
product=new Product();//房子是由工人建造出來的
}
@Override
void buildA() {
product.setBuildA("地基");
System.out.println("地基已經建好");
}
@Override
void buildB() {
product.setBuildB("鋼筋");
System.out.println("鋼筋已經建好");
}
@Override
void buildC() {
product.setBuildC("電線");
System.out.println("電線已經建好");
}
@Override
void buildD() {
product.setBuildD("粉刷");
System.out.println("粉刷已經建好");
}
@Override
Product getProduct() {
return product;//把工人建造的房子返回
}
}
但是實際上這個房子這個時候並沒有做出來,因爲沒有人去指揮工人按順序構建ABCD這四個任務。所以我們需要一個指揮者,去指揮工人建造房子,告訴他們任務順序。
//指揮者:核心!指揮工程如何構建,順序由他決定
public class Director {
//指揮者指揮工人按順序建造房子
public Product build(Builder builder){
builder.buildA();
builder.buildB();
builder.buildC();
builder.buildD();
return builder.getProduct();//工人建好的房子交給指揮者判斷是否完工
}
}
我們最後寫一個測試類,模擬我們的需求
//測試類
public class Test {
public static void main(String[] args) {
//找到建築公司
Director director=new Director();
//建築公司指揮工人建造房子
Product product = director.build(new Work());
//得到房子
System.out.println(product.toString());
}
}
通過運行結果我們可以得知,我們的需求得到了滿足,房子已經按順序建好,但是我們並不知道具體是怎麼建造的。我們只是找到了建築公司(new一個指揮者)。
如果我們不想按照建築公司的順序去建造房子,想改變施工順序,我們直接和建築公司溝通(修改Director類裏面方法執行順序)就可以了,無需改變工人(Work)。
//指揮者:核心!指揮工程如何構建,順序由他決定
public class Director {
//指揮者指揮工人按順序建造房子
public Product build(Builder builder){
//修改順序
builder.buildB();
builder.buildC();
builder.buildA();
builder.buildD();
return builder.getProduct();//工人建好的房子交給指揮者判斷是否完工
}
}
在測試類不變的情況下,再一次運行可得,施工順序已經改變。
但是有些時候,指揮者是不被需要的。比如我們去德克士點餐,我們(test)可以直接選擇套餐內容或者單點,然後服務員(Builder)去組合,最後將我們點的食物(product)送給我們。
我們可以使用靜態內部類去實現這種建造者模式,這種方式更靈活,更符合定義。內部有複雜對象的默認實現,使用時可以根據用戶需求自由定義更改內容。並且無需改變具體的構造方式,就可以生產出不同複雜的產品。
靜態內部類實現
先定義一個抽象的建造者和產品,在產品中有默認的順序和種類。
//抽象的建造者
public abstract class Builder {
abstract Builder buildA(String msg);//默認漢堡
abstract Builder buildB(String msg);//默認薯條
abstract Builder buildC(String msg);//默認炸雞
abstract Builder buildD(String msg);//默認可樂
abstract Product getProduct();//得到產品
}
//產品:套餐
public class Product {
//套餐默認是這些
private String buildA="漢堡";
private String buildB="薯條";
private String buildC="炸雞";
private String buildD="可樂";
public String getBuildA() {
return buildA;
}
public void setBuildA(String buildA) {
this.buildA = buildA;
}
public String getBuildB() {
return buildB;
}
public void setBuildB(String buildB) {
this.buildB = buildB;
}
public String getBuildC() {
return buildC;
}
public void setBuildC(String buildC) {
this.buildC = buildC;
}
public String getBuildD() {
return buildD;
}
public void setBuildD(String buildD) {
this.buildD = buildD;
}
@Override
public String toString() {
return "Product{" +
"buildA='" + buildA + '\'' +
", buildB='" + buildB + '\'' +
", buildC='" + buildC + '\'' +
", buildD='" + buildD + '\'' +
'}';
}
}
有了抽象的建造者,就可以寫他的實現類,具體的建造者
//具體的建造者:服務員
public class Work extends Builder{
private Product product;
public Work() {
product=new Product();//具體的建造者去創建產品
}
@Override
Builder buildA(String msg) {
product.setBuildA(msg);
return this;
}
@Override
Builder buildB(String msg) {
product.setBuildB(msg);
return this;
}
@Override
Builder buildC(String msg) {
product.setBuildC(msg);
return this;
}
@Override
Builder buildD(String msg) {
product.setBuildD(msg);
return this;
}
@Override
Product getProduct() {
return product;
}
}
如果我們不更改套餐,就按照默認套餐,我們只需要和服務員(具體的建造者)說一下即可。
public class Test {
public static void main(String[] args) {
Work work=new Work();
Product product=work.getProduct();
System.out.println(product.toString());
}
}
如果我們想要更改套餐內容,直接和服務員(具體的建造者)溝通即可,不需要知道具體是怎麼構建的。在用戶不知道創建過程和細節的情況下就可以直接創建複雜的對象。
public class Test {
public static void main(String[] args) {
Work work=new Work();
Product product=work.buildA("雞肉卷")
.buildD("雪碧")
.getProduct();
System.out.println(product.toString());
}
}
與原來相比,可以自由組合了,如果不組合也有默認套餐。同樣的構建過程可以創建不同的表示
與抽象工廠模式對比
- 與抽象工廠相比,建造者模式返回一個組裝好的完整產品。而抽象工廠模式返回一系列相關的產品,這些產品位於不同的產品等級,構成了一個產品族。
- 在抽象工廠模式中,客戶端實例化工廠類,然後調用工廠方法獲取所需產品對象。在建造者模式中,客戶端可以不直接調用建造者的相關方法,而通過指揮者類來知道如何生成對象,包括對象的組裝過程和建造步驟,她側重於一步步構造一個複雜對象,返回一個完整的對象。
- 如果抽象工廠模式看做一個汽車配件生產工廠,生產一個產品族的產品,那麼建造者模式就是一個汽車組裝工廠,通過對部件的組裝返回一輛完整的汽車。