三、堆疊和佇列
1、建造小人
用程式畫一個小人,簡單一點,要求就是,小人要有頭,身體,雙手,雙腳。
我們很簡單的就可以實現這個程式:
Pen pen = new Pen(Color.Black); Graphics graphics = pictureBox1.CreateGraphics(); graphics.DrawEllipse(pen, 50, 20, 30, 30);//頭 graphics.DrawRectangle(pen, 60, 50, 10, 50);//身體 graphics.DrawLine(pen, 60, 50, 40, 100);//左手 graphics.DrawLine(pen, 70, 50, 90, 100);//右手graphics.DrawLine(pen, 60, 100, 45, 150);//左腳 graphics.DrawLine(pen, 70, 100, 85, 150);//右腳
執行結果如下:
如果要求畫一個身體比較胖的小人呢?
這個也很簡單:
Pen pen = new Pen(Color.Black); Graphics graphics = pictureBox1.CreateGraphics(); graphics.DrawEllipse(pen, 50, 20, 30, 30); graphics.DrawEllipse(pen, 45, 50, 40, 50); graphics.DrawLine(pen,52, 50, 30, 100); graphics.DrawLine(pen, 80, 50, 100, 100); graphics.DrawLine(pen, 60, 100, 45, 150);
但是執行的結果如下:
原來是程式碼中少畫了一條腿。加上下面這個程式碼就可以了
graphics.DrawLine(pen, 70, 100, 85, 150);
2、建造小人2
畫人的時候,頭身手腳都是必不可少的,不管是什麼人物,開發的時候是不能少的。
目前,程式碼全寫在Form1.cs的窗體裡,要是需要在別的地方用這些畫小人的程式怎麼辦?
自然而然,我們會想到分離,建兩個類,一個是胖人的類,一個是瘦人的類。不管是誰都可以呼叫它了。
/// <summary> /// 瘦人的類 /// </summary> class PersonThinBuilder { private Graphics Graphics; private Pen Pen; /// <summary> /// 初始化的時候確定畫板和顏色 /// </summary> /// <param name="graphics"></param> /// <param name="pen"></param> public PersonThinBuilder(Graphics graphics, Pen pen) { Graphics = graphics; Pen = pen; } /// <summary> /// 建造小人 /// </summary> public void Build() { Graphics.DrawEllipse(Pen, 50, 20, 30, 30);//頭 Graphics.DrawRectangle(Pen, 60, 50, 10, 50);//身體 Graphics.DrawLine(Pen, 60, 50, 40, 100);//左手 Graphics.DrawLine(Pen, 70, 50, 90, 100);//右手 Graphics.DrawLine(Pen, 60, 100, 45, 150);//左腳 Graphics.DrawLine(Pen, 70, 100, 85, 150);//右腳 } }
胖人的類,也類似。然後,我在客戶端,值需要這樣寫就可以了。
Pen pen = new Pen(Color.Black); Graphics graphics = pictureBox1.CreateGraphics(); PersonThinBuilder personThinBuilder = new PersonThinBuilder(graphics, pen); personThinBuilder.Build(); Graphics graphicsFat = pictureBox2.CreateGraphics(); PersonFatBuilder personFatBuilder = new PersonFatBuilder(graphicsFat, pen); personFatBuilder.Build();
執行結果如下:
這樣寫,確實達到了可以複用這兩個畫小人的程式,但是缺胳膊少腿的可能依然存在,比如,現在,需要畫一個比較高的小人呢?
所以,最好的辦法就是規定,凡是建造小人,都必須要有頭、身體、雙手、雙腳。
3、建造者模式
仔細分析會發現,這裡建造小人的“過程”是穩定的,都需要頭身手腳,而具體建造的“細節”是不同的,有胖瘦高矮。但是對於使用者來講,我才不管你這些呢,我只想告訴你,我需要一個胖小人來遊戲,你給我建造一個就行了。
如果你需要將一個複雜物件的構建與它的表示分離,使得同樣的構建過程,可以建立不同的表示的意圖時,我們需要應用於一個設計模式,建造者(Builder)模式,又叫生成器模式。建造者模式可以將一個產品的內部表現與產品的生成過程分割開來,從而可以使一個建造過程生成具有不同的內部表現的產品物件。如果我們用了建造者模式,那麼使用者就只需要制定需要建造的型別就可以得到他們,而具體建造的過程和細節就不需要知道了。
建造者模式(Builder),將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。
那怎麼使用建造者模式呢?
一步一步來,首先我們要畫小人,都需要畫什麼呢?頭、身體、左手、右手、左腳、右腳。
所以我們先定義一個抽象的建造人的類,來把這個過程給穩定住,不讓任何人遺忘當中的任何一步。
abstract class PersonBuilder { protected Graphics Graphics; protected Pen Pen; public PersonBuilder(Graphics graphics, Pen pen) { Graphics = graphics; Pen = pen; } 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 graphics, Pen pen) : base(graphics, pen) { } public override void BuildArmLeft() { Graphics.DrawLine(Pen, 60, 50, 40, 100); } public override void BuildArmRight() { Graphics.DrawLine(Pen, 70, 50, 90, 100); } public override void BuildBody() { Graphics.DrawRectangle(Pen, 60, 50, 10, 50); } public override void BuildHead() { Graphics.DrawEllipse(Pen, 50, 20, 30, 30); } public override void BuildLegLeft() { Graphics.DrawLine(Pen, 60, 100, 45, 150); } public override void BuildLegRight() { Graphics.DrawLine(Pen, 70, 100, 85, 150); } }
當然,胖子或者高個子其實都用類似的程式碼去實現這個類就可以了。
但是,在客戶端呼叫是,還是需要知道頭身手腳這些方法,還是沒有解決。
我們還缺在建造者模式中一個很重要的類,指揮者(Director),用它來控制建造過程,也用它來隔離使用者與建造過程的關聯。
class PersonDirector { private PersonBuilder PersonBuilder; public PersonDirector(PersonBuilder personBuilder) { PersonBuilder = personBuilder; } public void CreatePerson() { PersonBuilder.BuildHead(); PersonBuilder.BuildBody(); PersonBuilder.BuildArmLeft(); PersonBuilder.BuildArmRight(); PersonBuilder.BuildLegLeft(); PersonBuilder.BuildLegRight(); } }
PersonDirector類的目的就是根據使用者的選擇來一步一步的建造小人,而建造的過程在指揮者這裡完成,使用者就不需要知道了,而且,由於這個過程每一步都是一定要做的,那就不會讓少了一隻手,少畫了一條腿的問題出現了。
程式碼結構圖如下:
那麼,在客戶端的程式碼,應該也不難實現了。
Pen pen = new Pen(Color.Black); PersonThinBuilder personThinBuilder = new PersonThinBuilder(pictureBox1.CreateGraphics(), pen); PersonDirector personDirectorThin = new PersonDirector(personThinBuilder); personDirectorThin.CreatePerson();
試想一下,如果需要增加一個高個子和矮個子的小人,應該怎麼做呢?
加兩個類,一個高個子類,和一個矮個子類,讓它們都去繼承PersonBuilder,然後在客戶端呼叫就可以了。
但是,如果需要細化一些,比如人的五官,手的上臂、前臂和手掌,大腿小腿這些,該怎麼辦呢?
這就需要權衡,如果這些細節是每個具體小人都需要構建的,那就應該要加進去,反之,就沒有必要。其實建造者模式是逐步建造產品的,所以建造者的Builder類裡面的那些建造方法必須要走狗普遍,以便為各種型別的具體建造者構造。
4、建造者模式解析
建造者模式(Builder)結構圖:
現在來看這張圖就不會感覺陌生了,總結一下,Builder是什麼?
概括的說,是為建立一個Product物件的各個部件指定的抽象介面。
ConcreteBuilder是什麼呢?
是具體的建造者,實現Builder介面,構造和裝配各個部件。Product當然就是那些具體的小人,產品角色了。
Director是什麼?
指揮者,是構建一個使用Builder介面的物件。
那都是什麼時候需要使用建造者模式呢?
主要是用於建立一些複雜的物件,這些物件的內部構建間的建造順序通常是穩定的,但物件內部的構建通常面臨著負責的變化。
我們試著把建造者模式的基本程式碼推演一下,一遍有一個更巨集觀的認識。
5、建造者模式基本程式碼
Product類——產品類,由多個部件組成。
class Product { IList<string> parts = new List<string>(); public void Add(string part) { parts.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; } }
ConcreteBuilder2類——具體建造者類。
class ConcreteBuilder2 : Builder { private Product product = new Product(); public override void BuildPartA() { product.Add("部件X"); } public override void BuildPartB() { product.Add("部件Y"); } public override Product GetResult() { return product; } }
Director類——指揮者類。
class Director { /// <summary> /// 用來指揮建造過程 /// </summary> /// <param name="builder"></param> public void Construct(Builder builder) { builder.BuildPartA(); builder.BuildPartB(); } }
客戶端程式碼,客戶不需要知道具體的構建過程。
static void Main(string[] args) { Director director = new Director(); Builder builder1 = new ConcreteBuilder1(); Builder builder2 = new ConcreteBuilder2(); director.Construct(builder1); //指揮者用ConcreteBuilder1的方法來建造產品 Product product1 = builder1.GetResult(); product1.Show(); director.Construct(builder2); //指揮者用ConcreteBuilder2的方法來建造產品 Product product2 = builder2.GetResult(); product2.Show(); Console.ReadLine(); }
執行結果如下:
所以說,建造者模式應該是在當建立複雜物件的演算法應該獨立於改物件的組成部分以及他們的裝配方式時適用的模式。