設(shè)計(jì)模式(4) 建造者模式

  • 什么是建造者模式
  • 經(jīng)典建造者模式的優(yōu)缺點(diǎn)
  • 對(duì)建造者模式的擴(kuò)展

什么是建造者模式

建造者模式將一個(gè)復(fù)雜的對(duì)象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示。創(chuàng)建者模式隱藏了復(fù)雜對(duì)象的創(chuàng)建過(guò)程,它把復(fù)雜對(duì)象的創(chuàng)建過(guò)程加以抽象,通過(guò)子類繼承或者重載的方式,動(dòng)態(tài)的創(chuàng)建具有復(fù)合屬性的對(duì)象。
雖然與工廠模式、抽象工廠模式、單件模式同為創(chuàng)建型模式,但建造者模式與之前學(xué)習(xí)的模式相比,更為關(guān)注創(chuàng)建過(guò)程的細(xì)節(jié),它一般用于創(chuàng)建復(fù)雜對(duì)象,從獨(dú)立創(chuàng)建每個(gè)部分到最后的組裝,它承擔(dān)每個(gè)步驟的工作。由于它把創(chuàng)建每個(gè)部分都獨(dú)立為一個(gè)單一的過(guò)程,因此不僅可以完成較為精細(xì)的創(chuàng)建,還可以根據(jù)創(chuàng)建步驟編排,生成不同的目標(biāo)實(shí)例。
GOF對(duì)建造者模式的描述是:
Separate the construction of a complex object from its representation so that the same construction process can create different representations..
— Design Patterns : Elements of Reusable Object-Oriented Software

創(chuàng)建者模式非常適用于產(chǎn)品局部加工過(guò)程變化較大,但組裝過(guò)程相對(duì)固定的場(chǎng)景。
比如電腦的組裝,基本的組裝過(guò)程是固定的,但是具體主板、CPU、顯卡、內(nèi)存、硬盤(pán)等選擇的品牌和型號(hào)可能差異很大;還有汽車的生產(chǎn)也是這樣,整體組裝過(guò)程基本相同,但不同品牌、價(jià)格的汽車在具體部件上差異很大。

其UML類圖如下:

markdown

其中包括三個(gè)角色:

  • IBuilder:負(fù)責(zé)描述創(chuàng)建一個(gè)產(chǎn)品各個(gè)組成的抽象接口。
  • Concrete Builder:實(shí)現(xiàn)IBuilder要求的內(nèi)容,并且提供一個(gè)獲得產(chǎn)品的方法。
  • Director:基于IBuilder定義的構(gòu)造產(chǎn)品的抽象步驟,指導(dǎo)Concrete Builder生成產(chǎn)品的過(guò)程。

這里的產(chǎn)品類型并沒(méi)有統(tǒng)一的IProduct接口,主要是因?yàn)榻?jīng)過(guò)不同ConcreteBuilder加工后的產(chǎn)品差別相對(duì)較大,給它一個(gè)公共的基準(zhǔn)抽象對(duì)象意義不大,

代碼實(shí)現(xiàn):

public class House
{
    public void AddWindowAndDoor() { }
    public void AddWallAndFloor() { }
    public void AddCeiling() { }
}

public class Car
{
    public void AddWheel() { }
    public void AddEngine() { }
    public void AddBody() { }
}

public interface IBuilder
{
    void BuildPart1();
    void BuildPart2();
    void BuildPart3();
}

public class CarBuilder : IBuilder
{
    private Car car;
    public void BuildPart1()
    {
        car.AddEngine();
    }
    public void BuildPart2()
    {
        car.AddWheel();
    }
    public void BuildPart3()
    {
        car.AddBody();
    }
}

public class HouseBuilder : IBuilder
{
    private House house;
    public void BuildPart1()
    {
        house.AddWallAndFloor();
    }
    public void BuildPart2()
    {
        house.AddCeiling();
    }
    public void BuildPart3()
    {
        house.AddWindowAndDoor();
    }
}

public class Director
{
    public void Construct(IBuilder builder)
    {
        builder.BuildPart1();
        builder.BuildPart2();
        builder.BuildPart3();
    }
}

調(diào)用:

[Test]
public void BuilderTest()
{
    Director director = new Director();
    CarBuilder carBuilder = new CarBuilder();
    HouseBuilder houseBuilder = new HouseBuilder();

    director.Construct(carBuilder);
    director.Construct(houseBuilder);

    Assert.AreEqual(typeof(Car), carBuilder.Car.GetType());
    Assert.AreEqual(typeof(House), houseBuilder.House.GetType());
}

經(jīng)典建造者模式的優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn):創(chuàng)建者模式將復(fù)雜對(duì)象的每個(gè)組成創(chuàng)建步驟暴露出來(lái),借助Director(或客戶程序自己)既可以選擇其執(zhí)行次序,也可以選擇要執(zhí)行哪些步驟。上述過(guò)程可以在應(yīng)用中動(dòng)態(tài)完成,相比較工廠方法和抽象工廠模式的一次性創(chuàng)建過(guò)程而言,創(chuàng)建者模式適合創(chuàng)建“更為復(fù)雜且每個(gè)組成變化較多”的類型。
  • 缺點(diǎn):但建造者模式也存在一些缺點(diǎn),比如正因?yàn)闀?huì)暴露出更多的執(zhí)行步驟,這就需要需要Director(或客戶程序)具有更多的領(lǐng)域知識(shí),使用不慎很容易造成相對(duì)更為緊密的耦合。

而且經(jīng)典建造者中IBuilder定義了數(shù)目固定的裝配動(dòng)作,而Director有把這些動(dòng)作的執(zhí)行順序也固定了,雖然建造者模式可以生產(chǎn)差異非常大的產(chǎn)品,但要求這些產(chǎn)品具有固定的裝配步驟,這就大大局限了這種模式的使用場(chǎng)景,因?yàn)楝F(xiàn)實(shí)中這樣的要求往往很難滿足。不同的產(chǎn)品往往具有數(shù)目不同的裝配動(dòng)作和次序,如果要把這樣的產(chǎn)品添加到建造者的生產(chǎn)列表中是做不到的,需要另外實(shí)現(xiàn)一套,改動(dòng)比較大。

對(duì)建造者模式的擴(kuò)展

上述問(wèn)題可以通過(guò)對(duì)經(jīng)典模式適當(dāng)優(yōu)化來(lái)解決。
IBuilder中定義的不同步驟可以進(jìn)一步抽象為一個(gè)Action,CarBuilder和HouseBuilder的代碼也很相似,可以提取一個(gè)基類,這部分代碼放在基類中。
代碼如下:

public interface IBuilder<T> where T : class, new()
{
    T BuildUp();
}

public abstract class BuilderBase<T> : IBuilder<T> where T : class, new()
{
    protected IList<Action> steps = new List<Action>();

    protected T product = new T();
    public virtual T BuildUp()
    {
        foreach (Action step in steps)
        {
            step();
        }
        return product;
    }
}

public class ConcreteCarBuilder : BuilderBase<Car>
{
    public ConcreteCarBuilder() : base()
    {
        steps.Add(product.AddEngine);
        steps.Add(product.AddWheel);
        steps.Add(product.AddBody);
    }
}

public class ConcreteHouseBuilder : BuilderBase<House>
{
    public ConcreteHouseBuilder() : base()
    {
        steps.Add(product.AddWallAndFloor);
        steps.Add(product.AddCeiling);
        steps.Add(product.AddWindowAndDoor);
    }
}

實(shí)體Builder兼做Director,具體的構(gòu)建步驟由實(shí)體Builder來(lái)決定,這樣做的好處是非常靈活,不同的Builder可以有不同數(shù)目的動(dòng)作,動(dòng)作的順序也可以自行安排。

調(diào)用:

[Test]
public void DelegateBuilderTest()
{
    IBuilder<Car> builder = new ConcreteCarBuilder();
    var product = builder.BuildUp();
    Assert.AreEqual(typeof(Car), product.GetType());

    IBuilder<House> builder1 = new ConcreteHouseBuilder();
    var product1 = builder1.BuildUp();
    Assert.AreEqual(typeof(House), product1.GetType());
}

參考書(shū)籍:
王翔著 《設(shè)計(jì)模式——基于C#的工程化實(shí)現(xiàn)及擴(kuò)展》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容