1. 程式人生 > >設計模式(十一)建造者模式

設計模式(十一)建造者模式

用程式畫一個小人,這在遊戲程式裡非常常見。現在簡單一點,要求是小人要有頭、身體、兩手、兩腳就可以了。

第一版 

Pen p = new Pen (Color.Yellow);
Graphics gThin = pictureBox1.CreateGraphics();

gThin.DrawEllipse(p,50,20,30,30);    // 頭
gThin.DrawRectangle(p,60,50,10,50);  // 身體
gThin.DrawLine(p,60,50,40,100);    //左手
gThin.DrawLine(p,70,50,90,100);    //右手
gThin.DrawLine(p,60,100,45,150);    //左腳
gThin.DrawLine(p,70,100,85,150);    //右腳

 寫得很快,那麼現在要畫一個身體比較胖的小人呢?

Graphics gFat = pictureBox2.CreateGraphics();

gFat.DrawEllipse(p,50,20,30,30);    // 頭
gFat.DrawEllipse(p,45,50,40,50);  // 身體
gFat.DrawLine(p,50,50,30,100);    //左手
gFat.DrawLine(p,80,50,100,100);    //右手
gFat.DrawLine(p,60,100,45,150);    //左腳
gFat.DrawLine(p,70,100,85,150);    //右腳

畫人的時候,頭身手腳是必不可少的,不管什麼人物,開發時是不能少的。 

 現在的程式碼全寫在Form1.cs的窗體裡,要是需要在別的地方用這些畫小人的程式怎麼辦?

第二版

(分離類)

class PersonThinBuilder{    // 瘦人的類
    private Graphics g;
    private Pen p;
    
    public PersonThinBuilder(Graphics g, Pen g){    // 初始化時確定畫板和顏色
        this.g=g;
        this.p=p;
    }
    
    public void Build(){    // 建造小人
        gThin.DrawEllipse(p,50,20,30,30);    // 頭
        gThin.DrawRectangle(p,60,50,10,50);  // 身體
        gThin.DrawLine(p,60,50,40,100);    //左手
        gThin.DrawLine(p,70,50,90,100);    //右手
        gThin.DrawLine(p,60,100,45,150);    //左腳
        gThin.DrawLine(p,70,100,85,150);    //右腳
    }
}

胖人的類也是相似的。然後在客戶端裡只需這樣寫就可以了:

Pen p = new Pen (Color.Yellow);

Graphics gThin = pictureBox1.CreateGraphics();
PersonThinBuilder ptb = new PersonThinBuilder(gThin,p);
ptb.Build();

Graphics gFat = pictureBox2.CreateGraphics();
PersonFatBuilder pfb = new PersonFatBuilder(gFat,p);
ptb.Build();

這樣達到了可以複用這兩個畫小人程式的目的。但是炒麵忘記放鹽的問題依然沒有解決。比如我現在需要你加一個高個的小人,你會不會因為程式設計不注意,又讓他缺胳膊少腿呢?

其實,最好的辦法,是規定凡是建造小人,都必須要有頭和身體,以及兩手兩腳。

建造者模式

如果需要將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示時,可以應用“建造者模式”,又叫生成器模式。建造者模式可以將一個產品的內部表象與產品的生成過程分割開來,從而可以使一個建造過程生成具有不同的內部表象的產品物件。如果我們用了建造者模式,那麼使用者就只需指定需要建造的型別就可以得到它們,而具體建造的過程和細節就不需知道了

那麼,怎麼應用建造者模式呢?

首先,我們定義一個抽象的建造人的類,來把這個過程給穩定住,不讓任何人遺忘當中的任何一步:

abstract class PersonBuilder{
    protected Graphics g;
    protected Pen p;    
    
    public PersonBuilder(Graphics g, Pen p){
        this.g=g;
        this.p=p;
    }

    public abstract void BuildHead();
    public abstract void BuildBody();
    public abstract void BuildArmLeft();
    public abstract void BuildArmRight();
    public abstract void BuildLegLeft();
    public abstract void BuildLegRight();
}

 然後,我們需要建造一個瘦的小人,則讓這個瘦子類去繼承這個抽象類,那就必須去重寫這些抽象方法了,否則編譯器不會讓你通過。

class PersonThinBuilder : PersonBuilder {
    public PersonThinBuilder(Graphics g, Pen p):base(g,p){}

    public override void BuildHead(){
        gThin.DrawEllipse(p,50,20,30,30);    // 頭
    }
    public override void BuildBody(){
        gThin.DrawRectangle(p,60,50,10,50);  // 身體
    }
    public override void BuildArmLeft(){
        gThin.DrawLine(p,60,50,40,100);    //左手
    }
    public override void BuildArmRight(){
        gThin.DrawLine(p,70,50,90,100);    //右手
    }
    public override void BuildLegLeft(){
        gThin.DrawLine(p,60,100,45,150);    //左腳
    }
    public override void BuildLegRight(){
        gThin.DrawLine(p,70,100,85,150);    //右腳
    }
}

胖人類似。

用指揮者(Director)來控制建造過程,也用它來隔離使用者與建造過程的關聯。

class PersonDirector{
    private PersonBuilder pb;
    // 使用者告訴指揮者需要什麼樣的小人
    public PerDirector(PersonBuilder pb){
        this.pb=pb;
    }
    public void CreatePerson(){
        // 根據使用者的選擇建造小人
        pb.BuildHead();
        pb.BuildBody();
        pb.BuildArmLeft();
        pb.BuildArmRight();
        pb.BuildLegLeft();
        pb.BuildLegRight();
    }
}

PersonDirector類的目的就是根據使用者的選擇來一步一步建造小人,而建造的過程在指揮者這裡完成了,使用者就不需要知道了。而且,由於這個過程每一步都是一定要做的,那就不會讓少畫了一隻手、少畫一條腿的問題出現了。

 程式碼結構圖:

客戶端程式碼:

Pen p = new Pen(Color.Yellow);
PersonThinBuilder ptb = new PersonThinBuilder(pictureBox1.CreateGraphics(),p);
PersonDirector pdThin = new PersonDirector(ptb);
pdThin.CreatePerson();

PersonFatBuilder ptb = new PersonFatBuilder(pictureBox1.CreateGraphics(),p);
PersonDirector pdFat = new PersonDirector(ptb);
pdFat.CreatePerson();

建造者模式

建造者模式主要用於建立一些複雜的物件,這些物件內部構建間的建造順序通常是穩定的,但物件內部的構建通常面臨著複雜的變化。建造者模式的好處就是使得建造程式碼與表示程式碼分離,由於建造者隱藏了該產品是如何組裝的,所以若需要改變一個產品的內部表示,只需要再定義一個具體的建造者就可以了。

Product類——產品類,由多個部件組成。

class Product{
    IList<string> parts = new List<string>();
    public void Add(string part){     // 增加產品部件
        patrs.Add(part);
    }

    public void Show(){
        Console.WriteLine("\n產品 建立 ----");
        // 列舉所有的產品部件
        foreach (string part in parts){
            Console.WriteLine(part);
        }
    }

}

Builder類——抽象建造者類,確定產品由兩個部件PartA和PartB組成,並宣告一個得到產品建造後結果的方法GetResult。

abstract class Builder{
    public abstract void BuildPartA();
    public abstract void BuildPartB();
    public abstract Product GetResult();
}

 ConcreteBuilder1類——具體建造者類。

class ConcreteBuilder1 : Builder{
    private Product product= new Product();

    public override void BuildPartA(){
        product.Add("部件A");
    }
    public override void BuildPartB(){
        product.Add("部件B");
    }
    public override Product GetResult(){
        return product;
    }
}

Director類——指揮者類。

class Director {
    public void Construct(Builder builder){
        // 用來指揮建造過程
        builder.BuildPartA();
        builder.BuildPartB();
    }
}

客戶端程式碼:

static void Main(string[] args){
    Director director = new Director();
    Builder b1 = new ConcreteBuilder1();
    Builder b2 = new ConcreteBuilder2();

    // 指揮者用ConcreteBuilder1的方法來建造產品
    director.Construct(b1);
    Product p1= b1.GetResult();
    p1.Show();

    // 指揮者用ConcreteBuilder2的方法來建造產品    
    director.Construct(b2);
    Product p2= b2.GetResult();
    p2.Show();

    Console.Read();
}

總結:

建造者模式是在當建立複雜物件的演算法應該獨立於該物件的組成部分以及它們的裝配方式時適用的模式。

 本章完。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 本文是連載文章,此為第十一章,學習將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示的建造者模式。

下一章:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------