1. 程式人生 > >設計模式之建造者模式——Builder

設計模式之建造者模式——Builder

一、概述

Builder模式,中文名為建造者模式,又名生成器模式、構建者模式等,是建立型設計模式之一。用於將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。

1.適用性:

  • 物件的建立比較複雜、有多種建立形式時
  • 建立複雜物件的演算法與物件內部組成和裝配是相對獨立的

2.UML類圖

  • Builder:定義建立Product各個部件的抽象介面
  • ConcreteBuilder:繼承自Builder,實現建立具體型別的Product所有部件的介面,並提供一個獲取最終產品的介面。
  • Director:藉助Builder,內部封裝建立產品的具體流程。

3.UML序列圖

  1. Client建立一個ConcreteBuilder
  2. Client通過傳入ConcreteBuilder建立一個Director
  3. Client使用Director構造產品
  4. Client使用ConcreteBuilder組裝並獲取最終產品

二、例項

想象一下,你作為造物主,你需要創造各種各樣的動物,比如狗和貓。檢視原始碼

1.在創造前,要先想好狗和貓大體是什麼樣子,很顯然,它們應該有頭、身體和腳。

abstract class Animal
{
    public string Head { get; set; }
    public string Body { get; set; }
    public string Foots { get; set; }
    
    public abstract void Display();
}

class Dog : Animal
{
    public override void Display()
    {
        Console.WriteLine("-------------- 狗的基本資訊 -------------------");
        Console.WriteLine($"頭:{Head}");
        Console.WriteLine($"身體:{Body}");
        Console.WriteLine($"腳:{Foots}");
    }
}

class Cat : Animal
{
    public override void Display()
    {
        Console.WriteLine("-------------- 貓的基本資訊 -------------------");
        Console.WriteLine($"頭:{Head}");
        Console.WriteLine($"身體:{Body}");
        Console.WriteLine($"腳:{Foots}");
    }
}

2.動物的基本特徵確定了,也就明確了要幹什麼活兒了——建立動物的頭、身體和腳。

interface IAnimalBuilder
{
    void SetHead();
    void SetBody();
    void SetFoots();
}

3.接下來就是考慮狗和貓各部位的建立細節了。不過,彆著急編碼,先來考慮一件事情:
  DogBuilder裡面除了需要實現IAnimalBuilder的所有介面外,還需要提供一個GetDog()的方法,用來把建立好的Dog給外部;同樣的CatBuilder中也需要提供一個GetCat()方法。這世間動物有多少種?一千?一萬?遠遠不止!在編碼的過程中很容易就忘記提供這個方法了。所以我們在抽象Builder和具體Builder中間插入一個抽象層IAnimalBuilder<TAnimal>

,所有的ConcreteBuilder都繼承自這個介面。

interface IAnimalBuilder<TAnimal> : IAnimalBuilder where TAnimal : Animal 
{
    TAnimal GetAnimal();
}

class DogBuilder : IAnimalBuilder<Dog>
{
    private readonly Dog _dog = new Dog();

    public void SetHead()
    {
        _dog.Head = "狗的頭";
    }

    public void SetBody()
    {
        _dog.Body = "狗的身體";
    }

    public void SetFoots()
    {
        _dog.Foots = "狗的腳";
    }

    public Dog GetAnimal()
    {
        return _dog;
    }
}

class CatBuilder : IAnimalBuilder<Cat>
{
    private readonly Cat _cat = new Cat();

    public void SetHead()
    {
        _cat.Head = "貓的頭";
    }

    public void SetBody()
    {
        _cat.Body = "貓的身體";
    }

    public void SetFoots()
    {
        _cat.Foots = "貓的腳";
    }

    public Cat GetAnimal()
    {
        return _cat;
    }
}

4.最後,只剩Director了,它來規劃Builder要建立哪些東西。

class Director
{
    private readonly IAnimalBuilder _builder;

    public Director(IAnimalBuilder builder)
    {
        _builder = builder;
    }

    public void Construct()
    {
        _builder.SetHead();
        _builder.SetBody();
        _builder.SetFoots();
    }
}

大功告成!接下來就嘗試一下吧

var dogBuilder = new DogBuilder();
var dogDirector = new Director(dogBuilder);
dogDirector.Construct();
dogBuilder.GetAnimal().Display();

var catBuilder = new CatBuilder();
var catDirector = new Director(catBuilder);
catDirector.Construct();
catBuilder.GetAnimal().Display();

三、變種

實際開發過程中,經常會遇到一種需求:建立物件時,物件的一些部分可以自由選擇設定或不設定,但是物件一旦建立完成,便不能對其進行任何修改。例如:ASP.NET Core框架中WebHost的建立。接下來,我們來模擬一下女媧造人。
1.同樣的,我們先設定好人的基本屬性

class Person
{
    public string Name { get; set; }
    public int Gender { get; set; }
    public int Age { get; set; }
}

2.接下來,我們來定義IPersonBuilder介面,除了Build方法外,我們都返回了IPersonBuilder,對於習慣於使用Linq的.Net開發人員來說,再熟悉不過了,它最大的好處就是能夠讓我們使用Fluent的方式來編寫程式碼。

public interface IPersonBuilder
{
    //注意,這裡有問題
    Person Build();
    IPersonBuilder SetName(string name);
    IPersonBuilder SetGender(int gender);
    IPersonBuilder SetAge(int age);
}

你發現什麼問題了嗎?沒錯,這違背了面向物件五大原則之一——依賴倒置(抽象不應該依賴具體);而且,這也不滿足“物件建立後不得進行更改”的需求。所以,我們需要提取出一個抽象層IPersion

public interface IPerson
{
    string Name { get; }
    int Gender { get; }
    int Age { get; }
}

class Person : IPerson
{
    public string Name { get; set; }
    public int Gender { get; set; }
    public int Age { get; set; }
}

public interface IPersonBuilder
{
    IPerson Build();
    IPersonBuilder SetName(string name);
    IPersonBuilder SetGender(int gender);
    IPersonBuilder SetAge(int age);
}

3.下一步就是構建PersonBuilder了,用來實現IPersonBuilder介面的具體邏輯。

class PersonBuilder : IPersonBuilder
{
    private readonly Person _person = new Person();

    public IPerson Build()
    {
        return _person;
    }

    public IPersonBuilder SetName(string name)
    {
        _person.Name = name;
        return this;
    }

    public IPersonBuilder SetGender(int gender)
    {
        _person.Gender = gender;
        return this;
    }

    public IPersonBuilder SetAge(int age)
    {
        _person.Age = age;
        return this;
    }      
}

//提供一個輔助類,用來幫 Client 建立 PersonBuilder
public class PersonBuilderHelper
{
    public static IPersonBuilder CreatePersonBuilder()
    {
        return new PersonBuilder();
    }
}

4.Ok,大功告成!來測試一下吧

var person = PersonBuilderHelper.CreatePersonBuilder()
    .SetAge(20)
    .SetName("jjj")
    .Build();
//輸出:jjj,0,20
Console.WriteLine($"{person.Name},{person.Gender},{person.Age}");

四、總結

經典版與變種版本的區別:

  • 經典版傾向於將構建規劃封裝起來,Client不關心內部邏輯;而變種版本消除了Director,使得Client擁有更多的主動權,可以自由地進行構建。
  • 經典版需要使用Director進行構建,然後使用Builder進行組裝返回結果;變種版本則直接使用Builder進行鏈式構建並組裝返回結果,結構更加清晰明瞭。
  • 經典版常用於多種產品的構建是比較固定的情況,Director種類也不宜過多,且必須適應所有產品;而變種版更傾向於某一種產品的構建,構建方式不固定、相當複雜的情況。

檢視原始碼

如果有新發現會進行補充