1. 程式人生 > >創建型模式:建造者模式

創建型模式:建造者模式

參考資料 系列 建造者 幫助 lex 興趣 table 不同的 vat

個人公眾號原文:
創建型模式:建造者模式

技術分享圖片

五大創建型模式之四:建造者模式。

簡介

姓名 :建造者模式

英文名 :Builder Pattern

價值觀 :專治丟三落四

個人介紹

Separate the construction of a complex object from its representation so that the same construction process can create different representations.
將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
(來自《設計模式之禪》)

今天給大家介紹的是建造者模式。建造者模式的使用場景是:創建復雜的對象。什麽才能算復雜對象?如果一個對象只需要通過 new XXX() 的方式創建,那就算是一個簡單對象;如果需要 new XXX(),並且還要設置很多屬性,那這種就可以稱為復雜對象了,因為它的構建過程比較復雜。采用建造者模式,可以把這個復雜的構建過程抽離開,使它不依賴創建者。下面我們通過故事來講解。

你要的故事

還記得小時候剛開始學煮湯,不了解怎麽煮,第一次是煮了板栗排骨湯,因為板栗比較難熟,所以是跟排骨一起下鍋,然後煮了 40 分鐘,加了鹽就可以出鍋啦。第二次煮了冬瓜排骨湯,不懂得冬瓜容易熟,就和排骨一起下鍋,煮了也差不多 40 分鐘,下了鹽和香菜,發現怎麽這湯有點濃,冬瓜咋都不見了(全都煮透了),我媽看到這鍋湯,才跟我說冬瓜容易熟,得先熬排骨,再放冬瓜進去煮。那時才發現熬湯特麽還有這差異。

上面是背景哈,接下來我們來實現這個煲湯過程,冬瓜排骨湯是先加排骨,熬制 30 分鐘,加冬瓜,熬制 18 分鐘,加鹽加香菜;板栗排骨湯是先加排骨和板栗,熬制 40 分鐘,再加鹽。這湯都需要加肉、加菜、熬制、加配料。我們使用下面代碼實現這個煲冬瓜排骨湯和板栗排骨湯的過程。

public class NoBuilderTest {

    public static void main(String[] args) {
        // 熬制冬瓜排骨湯
        DongGuaPaiGuSoup dongGuaPaiGuSoup = new DongGuaPaiGuSoup();
        // 加排骨
        dongGuaPaiGuSoup.addMeat();
        // 熬制 30 分鐘
        dongGuaPaiGuSoup.waitMinute(30);
        // 加冬瓜
        dongGuaPaiGuSoup.addVegetables();
        // 熬制 10 分鐘
        dongGuaPaiGuSoup.waitMinute(10);
        // 加鹽加香菜
        dongGuaPaiGuSoup.addIngredients();

        // 熬制板栗排骨湯
        BanLiPaiGuSoup banLiPaiGuSoup = new BanLiPaiGuSoup();
        // 加排骨
        banLiPaiGuSoup.addMeat();
        // 加板栗
        banLiPaiGuSoup.addVegetables();
        // 熬制 40 分鐘
        banLiPaiGuSoup.waitMinute(40);
        // 加鹽
        banLiPaiGuSoup.addIngredients();
    }

}

/**
 * 煲湯接口
 */
interface Soup {

    /** 加肉 */
    void addMeat();
    /** 加菜 */
    void addVegetables();
    /** 熬制 */
    void waitMinute(int minute);
    /** 加配料 */
    void addIngredients();

}

/**
 * 冬瓜排骨湯
 */
class DongGuaPaiGuSoup implements Soup {

    @Override
    public void addMeat() {
        System.out.println("加排骨");
    }

    @Override
    public void addVegetables() {
        System.out.println("加冬瓜");
    }

    @Override
    public void waitMinute(int minute) {
        System.out.println("熬制 " + minute + " 分鐘");
    }

    @Override
    public void addIngredients() {
        System.out.println("加鹽、加香菜");
    }
}

/**
 * 板栗排骨湯
 */
class BanLiPaiGuSoup implements Soup {

    @Override
    public void addMeat() {
        System.out.println("加排骨");
    }

    @Override
    public void addVegetables() {
        System.out.println("加板栗");
    }

    @Override
    public void waitMinute(int minute) {
        System.out.println("熬制 " + minute + " 分鐘");
    }

    @Override
    public void addIngredients() {
        System.out.println("加鹽");
    }
}

上面代碼簡單實現了煲冬瓜排骨湯和板栗排骨湯。煲湯我們要關註的點是:各操作的順序,是先加肉先煮再加菜,還是肉和菜一起放進鍋煮。上面代碼中,這個過程是誰控制的?是煲湯的人,所以順序由煲湯的人決定,甚至有可能忘記放配料啥的,這樣子的湯就味道不夠好。那怎麽去解決這些問題?

我們通過建造者模式可以解決上面的 2 個問題:煲湯順序問題和忘記加配料這種丟三落四行為。我們將這個煲湯順序從煲湯者分離開來,讓煲湯者只需要決定煲什麽湯就好,讓建造者來保證煲湯順序問題和防止漏加配料。

我們用一個 SoupBuilder 來規範化煲湯過程,方法 buildSoup 給實現者提供一個設置煲湯順序的地方。因為冬瓜排骨湯和板栗排骨湯熬制的過程不一樣,所以分別用 DongGuaPaiGuSoupBuilder 和 BanLiPaiGuSoupBuilder 來具體實現冬瓜排骨湯和板栗排骨湯的熬制過程,也就是消除熬制過程和煲湯者的依賴關系。 Director 則相當於一個菜單,提供為熬湯者來選擇熬什麽湯。具體代碼如下所示。

public class BuilderTest {

    public static void main(String[] args) {
        Director director = new Director();
        // 熬制冬瓜排骨湯
        director.buildDongGuaPaiGuSoup();
        // 熬制板栗排骨湯
        director.buildBanLiPaiGuSoup();
    }

}

/**
 * 煲湯建造接口
 */
interface SoupBuilder {
    void buildSoup();
    Soup getSoup();
}

/**
 * 冬瓜排骨湯建造者
 */
class DongGuaPaiGuSoupBuilder implements SoupBuilder {

    private DongGuaPaiGuSoup dongGuaPaiGuSoup = new DongGuaPaiGuSoup();

    @Override
    public void buildSoup() {
        // 加排骨
        dongGuaPaiGuSoup.addMeat();
        // 熬制 30 分鐘
        dongGuaPaiGuSoup.waitMinute(30);
        // 加冬瓜
        dongGuaPaiGuSoup.addVegetables();
        // 熬制 10 分鐘
        dongGuaPaiGuSoup.waitMinute(10);
        // 加鹽加香菜
        dongGuaPaiGuSoup.addIngredients();
    }

    @Override
    public Soup getSoup() {
        return dongGuaPaiGuSoup;
    }
}

/**
 * 板栗排骨湯建造者
 */
class BanLiPaiGuSoupBuilder implements SoupBuilder {

    BanLiPaiGuSoup banLiPaiGuSoup = new BanLiPaiGuSoup();

    @Override
    public void buildSoup() {
        // 加排骨
        banLiPaiGuSoup.addMeat();
        // 加板栗
        banLiPaiGuSoup.addVegetables();
        // 熬制 40 分鐘
        banLiPaiGuSoup.waitMinute(40);
        // 加鹽
        banLiPaiGuSoup.addIngredients();
    }

    @Override
    public Soup getSoup() {
        return banLiPaiGuSoup;
    }
}

/**
 * 生產方
 */
class Director {
    private DongGuaPaiGuSoupBuilder dongGuaPaiGuSoupBuilder = new DongGuaPaiGuSoupBuilder();
    private BanLiPaiGuSoupBuilder banLiPaiGuSoupBuilder = new BanLiPaiGuSoupBuilder();

    /**
     * 熬制冬瓜排骨湯
     */
    public DongGuaPaiGuSoup buildDongGuaPaiGuSoup() {
        dongGuaPaiGuSoupBuilder.buildSoup();
        return (DongGuaPaiGuSoup) dongGuaPaiGuSoupBuilder.getSoup();
    }

    /**
     * 熬制板栗排骨湯
     */
    public BanLiPaiGuSoup buildBanLiPaiGuSoup() {
        banLiPaiGuSoupBuilder.buildSoup();
        return (BanLiPaiGuSoup) banLiPaiGuSoupBuilder.getSoup();
    }

}

通過用建造者實現,是不是保證了熬制湯的順序並且一定會加夠料?感受一下其中的奧秘吧。

代碼:
Builder Pattern

總結

通過建造者模式,可以把本來強依賴的東西解綁,不僅僅解決依賴問題,還提高了封裝性,讓使用者不用明白內部的細節,用上面的例子說就熬湯不用關心怎麽熬制的過程,就像我們想喝冬瓜排骨湯,告訴媽媽,媽媽熬制,我們並不知道是怎麽熬制的。

參考資料:《大話設計模式》、《Java設計模式》、《設計模式之禪》、《研磨設計模式》、《Head First 設計模式》

推薦閱讀:
創建型模式:單例模式
創建型模式:工廠方法
創建型模式:抽象工廠
公眾號之設計模式系列文章

希望文章對您有所幫助,設計模式系列會持續更新,感興趣的同學可以關註公眾號:LieBrother,第一時間獲取文章推送閱讀,也可以一起交流,交個朋友。

技術分享圖片

創建型模式:建造者模式