設計模式(4) 建造者模式

  • 什麼是建造者模式
  • 經典建造者模式的優缺點
  • 對建造者模式的擴展

什麼是建造者模式

建造者模式將一個複雜的對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。創建者模式隱藏了複雜對象的創建過程,它把複雜對象的創建過程加以抽象,通過子類繼承或者重載的方式,動態的創建具有複合屬性的對象。
雖然與工廠模式、抽象工廠模式、單件模式同爲創建型模式,但建造者模式與之前學習的模式相比,更爲關注創建過程的細節,它一般用於創建複雜對象,從獨立創建每個部分到最後的組裝,它承擔每個步驟的工作。由於它把創建每個部分都獨立爲一個單一的過程,因此不僅可以完成較爲精細的創建,還可以根據創建步驟編排,生成不同的目標實例。
GOF對建造者模式的描述是:
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

創建者模式非常適用於產品局部加工過程變化較大,但組裝過程相對固定的場景。
比如電腦的組裝,基本的組裝過程是固定的,但是具體主板、CPU、顯卡、內存、硬盤等選擇的品牌和型號可能差異很大;還有汽車的生產也是這樣,整體組裝過程基本相同,但不同品牌、價格的汽車在具體部件上差異很大。

其UML類圖如下:
在這裏插入圖片描述

其中包括三個角色:

  • IBuilder:負責描述創建一個產品各個組成的抽象接口。
  • Concrete Builder:實現IBuilder要求的內容,並且提供一個獲得產品的方法。
  • Director:基於IBuilder定義的構造產品的抽象步驟,指導Concrete Builder生成產品的過程。

這裏的產品類型並沒有統一的IProduct接口,主要是因爲經過不同ConcreteBuilder加工後的產品差別相對較大,給它一個公共的基準抽象對象意義不大,

代碼實現:

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();
    }
}

調用:

[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());
}

經典建造者模式的優缺點

  • 優點:創建者模式將複雜對象的每個組成創建步驟暴露出來,藉助Director(或客戶程序自己)既可以選擇其執行次序,也可以選擇要執行哪些步驟。上述過程可以在應用中動態完成,相比較工廠方法和抽象工廠模式的一次性創建過程而言,創建者模式適合創建“更爲複雜且每個組成變化較多”的類型。
  • 缺點:但建造者模式也存在一些缺點,比如正因爲會暴露出更多的執行步驟,這就需要需要Director(或客戶程序)具有更多的領域知識,使用不慎很容易造成相對更爲緊密的耦合。

而且經典建造者中IBuilder定義了數目固定的裝配動作,而Director有把這些動作的執行順序也固定了,雖然建造者模式可以生產差異非常大的產品,但要求這些產品具有固定的裝配步驟,這就大大侷限了這種模式的使用場景,因爲現實中這樣的要求往往很難滿足。不同的產品往往具有數目不同的裝配動作和次序,如果要把這樣的產品添加到建造者的生產列表中是做不到的,需要另外實現一套,改動比較大。

對建造者模式的擴展

上述問題可以通過對經典模式適當優化來解決。
IBuilder中定義的不同步驟可以進一步抽象爲一個Action,CarBuilder和HouseBuilder的代碼也很相似,可以提取一個基類,這部分代碼放在基類中。
代碼如下:

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);
    }
}

實體Builder兼做Director,具體的構建步驟由實體Builder來決定,這樣做的好處是非常靈活,不同的Builder可以有不同數目的動作,動作的順序也可以自行安排。

調用:

[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());
}

參考書籍:
王翔著 《設計模式——基於C#的工程化實現及擴展》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章