設計模式之建造者模式——Builder
一、概述
Builder模式,中文名為建造者模式,又名生成器模式、構建者模式等,是建立型設計模式之一。用於將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。
1.適用性:
- 物件的建立比較複雜、有多種建立形式時
- 建立複雜物件的演算法與物件內部組成和裝配是相對獨立的
2.UML類圖
Builder
:定義建立Product
各個部件的抽象介面ConcreteBuilder
:繼承自Builder
,實現建立具體型別的Product
所有部件的介面,並提供一個獲取最終產品的介面。Director
:藉助Builder
,內部封裝建立產品的具體流程。
3.UML序列圖
- Client建立一個ConcreteBuilder
- Client通過傳入ConcreteBuilder建立一個Director
- Client使用Director構造產品
- 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>
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種類也不宜過多,且必須適應所有產品;而變種版更傾向於某一種產品的構建,構建方式不固定、相當複雜的情況。
檢視原始碼
如果有新發現會進行補充