設(shè)計(jì)模式概覽
1.簡(jiǎn)單工廠模式結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
public class Operation{ private double _numberA = 0; private double _numberB = 0; public double NumberA { get { return _numberA; } t { _numberA = value; } } public double NumberB { get { return _numberB; } t { _numberB = value; } } public virtual double GetResult() { double result = 0; return result; }}2.策略模式
策略模式定義了算法家族,分別封裝起來(lái),讓它們之間可以互相替換,此模式讓算法的變化,不會(huì)影響到使用算法的客戶(hù)。
在實(shí)踐中,我們發(fā)現(xiàn)可以用它來(lái)封裝幾乎任何類(lèi)型的規(guī)則,只要在分析過(guò)程中聽(tīng)到需要在不同時(shí)間應(yīng)用不同的業(yè)務(wù)規(guī)則,就可以考慮使用策略模式處理這種變化的可能性。
結(jié)構(gòu)圖
CashContext類(lèi)定義
客戶(hù)端代碼
策略與簡(jiǎn)單工廠結(jié)合
簡(jiǎn)單工廠模式我需要讓客戶(hù)端認(rèn)識(shí)兩個(gè)類(lèi),CashSuper和CashFactory,而策略模式與簡(jiǎn)單工廠結(jié)合的用法,客戶(hù)端就只需要認(rèn)識(shí)一個(gè)類(lèi)CashContext就可以了。耦合更加降低。
改造后的CashContext
客戶(hù)端代碼
3.單一職責(zé)原則一個(gè)類(lèi)而言,應(yīng)該僅有一個(gè)引起它變化的原因。
軟件設(shè)計(jì)真正要做的許多內(nèi)容,就是發(fā)現(xiàn)職責(zé)并把那些職責(zé)相互分離。
4.開(kāi)放-封閉原則這個(gè)原則其實(shí)是有兩個(gè)特征,一個(gè)是說(shuō)對(duì)于擴(kuò)展是開(kāi)放的,另一個(gè)是說(shuō)對(duì)于更改是封閉的。開(kāi)放-封閉原則是面向?qū)ο笤O(shè)計(jì)的核心所在。遵循這個(gè)原則可以帶來(lái)面向?qū)ο蠹夹g(shù)所聲稱(chēng)的巨大好處,也就是可維護(hù)、可擴(kuò)展、可復(fù)用、靈活性好。開(kāi)發(fā)人員應(yīng)該僅對(duì)程序中呈現(xiàn)出頻繁變化的那些部分做出抽象,然而,對(duì)于應(yīng)用程序中的每個(gè)部分都刻意地進(jìn)行抽象同樣不是一個(gè)好主意。拒絕不成熟的抽象和抽象本身一樣重要。
5.依賴(lài)倒轉(zhuǎn)原則高層模塊不應(yīng)該依賴(lài)底層模塊。兩個(gè)模塊都應(yīng)該依賴(lài)抽象。抽象不應(yīng)該依賴(lài)細(xì)節(jié)。細(xì)節(jié)應(yīng)該依賴(lài)抽象關(guān)于這個(gè)原則的理解:首先理解什么是高層模塊,什么是底層模塊,高層模塊一般是指我們的業(yè)務(wù)邏輯,底層模塊一般是指一些跟業(yè)務(wù)無(wú)關(guān)的通用功能。再來(lái)理解為什么兩個(gè)都應(yīng)該依賴(lài)抽象,例如,我們做的項(xiàng)目大多要訪問(wèn)數(shù)據(jù)庫(kù),所以我們就把訪問(wèn)數(shù)據(jù)庫(kù)的代碼寫(xiě)成了函數(shù),每次做新項(xiàng)目時(shí)就去調(diào)用這些函數(shù),這也就叫做高層模塊依賴(lài)低層模塊。但是我們要做新項(xiàng)目時(shí),發(fā)現(xiàn)業(yè)務(wù)邏輯的高層模塊都是一樣的,但客戶(hù)卻希望使用不同的數(shù)據(jù)庫(kù)或存儲(chǔ)信息方式,這時(shí)就出現(xiàn)麻煩了。我們希望能再次利用這些高層模塊,但高層模塊都是與低層的訪問(wèn)數(shù)據(jù)庫(kù)綁定在一起的,沒(méi)辦法復(fù)用這些高層模塊,這就非常糟糕了。而如果不管高層模塊還是低層模塊,它們都依賴(lài)于抽象,具體一點(diǎn)就是接口或抽象類(lèi),只要接口是穩(wěn)定的,那么任何一個(gè)的更改都不用擔(dān)心其他受到影響,這就使得無(wú)論高層模塊還是低層模塊都可以很容易地被復(fù)用。這才是最好的辦法。為什么依賴(lài)了抽象的接口或抽象類(lèi),就不怕更改呢,這里涉及一個(gè)另一個(gè)原則:
里氏代換原則
子類(lèi)型必須能夠替換掉他們的父類(lèi)型。它的白話(huà)翻譯就是一個(gè)軟件實(shí)體如果使用的是一個(gè)父類(lèi)的話(huà),那么一定適用于其子類(lèi),而且它察覺(jué)不出父類(lèi)對(duì)象和子類(lèi)對(duì)象的區(qū)別。也就是說(shuō),在軟件里面,把父類(lèi)都替換成它的子類(lèi),程序的行為沒(méi)有變化。
6.裝飾模式結(jié)構(gòu)圖
這個(gè)模式還可以變通:如果只有一個(gè)ConcreteComponent類(lèi)而沒(méi)有抽象的Component類(lèi),那么Decorator類(lèi)可以是ConcreteComponent的一個(gè)子類(lèi)。同樣道理,如果只有一個(gè)ConcreteDecorator類(lèi),那么就沒(méi)有必要建立一個(gè)單獨(dú)的Decorator類(lèi),而可以把Decorator和ConcreteDecorator的責(zé)任合并成一個(gè)類(lèi)。
代碼實(shí)現(xiàn)
abstract class Component{ public abstract void Operation();}class ConcreteComponent : Component{ public override void Operation() { Console.WriteLine("具體對(duì)象的操作"); }}
裝飾模式總結(jié):裝飾模式是為已有功能動(dòng)態(tài)地添加更多功能的一種方式。什么時(shí)候用它?
當(dāng)系統(tǒng)需要新功能的時(shí)候,而這些新加入的東西僅僅是為了滿(mǎn)足一些只在某種特定情況下才會(huì)執(zhí)行的特殊行為的需要,如果我們直接在主類(lèi)里添加新的代碼會(huì)增加了主類(lèi)的復(fù)雜度。而裝飾模式卻提供了一個(gè)非常好的解決方案,它把每個(gè)要裝飾的功能放在單獨(dú)的類(lèi)中,并讓這個(gè)類(lèi)包裝它所要裝飾的對(duì)象,因此,當(dāng)需要執(zhí)行特殊行為時(shí),客戶(hù)代碼就可以在運(yùn)行時(shí)根據(jù)需要有選擇地、按順序地使用裝飾功能包裝對(duì)象了。
裝飾模式的優(yōu)點(diǎn)總結(jié)下來(lái)就是,把類(lèi)中的裝飾功能從類(lèi)中搬移去除,這樣可以簡(jiǎn)化原有的類(lèi)。
7.代理模式為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)。
結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
//Subject類(lèi),定義了RealSubject和Proxy的共用接口,這樣就在任何使用RealSubject的地方都可以使用Proxyabstract class Subject{ public abstract void Request();}//RealSubject類(lèi),定義Proxy所代表的真實(shí)實(shí)體class RealSubject : Subject{ public override void Request() { Console.WriteLine("真實(shí)的請(qǐng)求"); }}//Proxy類(lèi),保存一個(gè)引用使得代理可以訪問(wèn)實(shí)體,并提供一個(gè)與Subject的接口相同的接口,這樣代理就可以用來(lái)替代實(shí)體class Proxy : Subject{ RealSubject realSubject; public override void Request() { if(realSubject == null) { realSubject = new RealSubject(); } realSubject.Request(); }}//客戶(hù)端代碼static void Main(string[] args){ Proxy proxy = new Proxy(); proxy.Request(); Console.Read();}
代理模式的應(yīng)用
第一種應(yīng)用是遠(yuǎn)程代理,也就是為一個(gè)對(duì)象在不同的地址空間提供局部代表。這樣可以隱藏一個(gè)對(duì)象存在于不同地址空間的事實(shí)。典型的應(yīng)用有WebService服務(wù)代理。第二種應(yīng)用是虛擬代理,是根據(jù)需要?jiǎng)?chuàng)建開(kāi)銷(xiāo)很大的對(duì)象。通過(guò)它來(lái)存放實(shí)例化需要很長(zhǎng)時(shí)間的真實(shí)對(duì)象。這樣就可以達(dá)到性能的最優(yōu)化,比如說(shuō)你打開(kāi)一個(gè)很大的HTML網(wǎng)頁(yè)時(shí),里面可能有很多的文字和圖片,但你還是可以很快打開(kāi)它,此時(shí)你所看到的是所有的文字,但圖片卻是一張一張地下載后才能看到。那些未打開(kāi)的圖片框,就是通過(guò)虛擬代理來(lái)替代了真實(shí)的圖片,此時(shí)代理存儲(chǔ)了真實(shí)圖片的路徑和尺寸。第三種應(yīng)用是安全代理,用來(lái)控制真實(shí)對(duì)象訪問(wèn)時(shí)的權(quán)限。一般用于對(duì)象應(yīng)該有不同的訪問(wèn)權(quán)限的時(shí)候。第四種是智能指引,是指當(dāng)調(diào)用真實(shí)的對(duì)象時(shí),代理處理另外一些事。如計(jì)算真實(shí)對(duì)象的引用次數(shù),這樣當(dāng)該對(duì)象沒(méi)有引用時(shí),可以自動(dòng)釋放它;或當(dāng)?shù)谝淮我靡粋€(gè)持久對(duì)象時(shí),將它裝入內(nèi)存;或在訪問(wèn)一個(gè)實(shí)際對(duì)象前,檢查是否已經(jīng)鎖定它,以確保其他對(duì)象不能改變它。它們都是通過(guò)代理在訪問(wèn)一個(gè)對(duì)象時(shí)附加一些內(nèi)務(wù)處理。8.工廠方法模式結(jié)構(gòu)圖(以前面計(jì)算的例子做示例)
代碼實(shí)現(xiàn)
interface IFactory
{
Operation CreateOperation();
}
IFactory operFactory = new AddFactory();
Operation oper = operFactory.CreateOperation();
oper.NumberA = 1;
oper.NumberB = 2;
double result=oper.GetResult();
簡(jiǎn)單工廠vs工廠方法
簡(jiǎn)單工廠模式的最大優(yōu)點(diǎn)在于工廠類(lèi)中包含了必要的邏輯判斷,根據(jù)客戶(hù)端的選擇條件動(dòng)態(tài)實(shí)例化相關(guān)的類(lèi),對(duì)于客戶(hù)端來(lái)說(shuō),去除了與具體產(chǎn)品的依賴(lài)。就像你的計(jì)算器,讓客戶(hù)端不用管該用哪個(gè)類(lèi)的實(shí)例,只需要把‘+’給工廠,工廠自動(dòng)就給出了相應(yīng)的實(shí)例,客戶(hù)端只要去做運(yùn)算就可以了,不同的實(shí)例會(huì)實(shí)現(xiàn)不同的運(yùn)算。但問(wèn)題也就在這里,如你所說(shuō),如果要加一個(gè)‘求M數(shù)的N次方’的功能,我們是一定需要給運(yùn)算工廠類(lèi)的方法里加‘Ca’的分支條件的,修改原有的類(lèi),違背了開(kāi)放-封閉原則。而對(duì)于工廠方法,則只需要增加此功能的運(yùn)算類(lèi)和相應(yīng)的工廠類(lèi)就可以了。工廠方法模式是簡(jiǎn)單工廠模式的進(jìn)一步抽象和推廣。由于使用了多態(tài)性,工廠方法模式保持了簡(jiǎn)單工廠模式的優(yōu)點(diǎn),而且克服了它的缺點(diǎn)。但缺點(diǎn)是由于每加一個(gè)產(chǎn)品,就需要加一個(gè)產(chǎn)品工廠的類(lèi),增加了額外的開(kāi)發(fā)量。
注意:工廠方法模式實(shí)現(xiàn)時(shí),客戶(hù)端需要決定實(shí)例化哪一個(gè)工廠來(lái)實(shí)現(xiàn)運(yùn)算類(lèi),選擇判斷的問(wèn)題還是存在的,也就是說(shuō),工廠方法把簡(jiǎn)單工廠的內(nèi)部邏輯判斷移到了客戶(hù)端代碼來(lái)進(jìn)行。你想要加功能,本來(lái)是改工廠類(lèi)的,而現(xiàn)在是修改客戶(hù)端,這個(gè)問(wèn)題可以通過(guò)反射解決。
9.原型模式原型模式其實(shí)就是從一個(gè)對(duì)象再創(chuàng)建另外一個(gè)可定制的對(duì)象,而且不需知道任何創(chuàng)建的細(xì)節(jié)。
結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
原型類(lèi)
具體原型
客戶(hù)端代碼
注意:對(duì)于.NET而言,那個(gè)原型抽象類(lèi)Prototype是用不著的,因?yàn)榭寺?shí)在是太常用了,所以.NET在System命名空間中提供了ICloneable接口,其中就是唯一的一個(gè)方法Clone,這樣你就只需要實(shí)現(xiàn)這個(gè)接口就可以完成原型模式了。
原型模式要注意深復(fù)制和淺復(fù)制的問(wèn)題。在一些特定場(chǎng)合,會(huì)經(jīng)常涉及深復(fù)制或淺復(fù)制,比如說(shuō),數(shù)據(jù)集對(duì)象DataSet,它就有Clone方法和Copy方法,Clone方法用來(lái)復(fù)制DataSet的結(jié)構(gòu),但不復(fù)制DataSet的數(shù)據(jù),實(shí)現(xiàn)了原型模式的淺復(fù)制。Copy方法不但復(fù)制結(jié)構(gòu),也復(fù)制數(shù)據(jù),其實(shí)就是實(shí)現(xiàn)了原型模式的深復(fù)制
10.模板方法模式定義一個(gè)操作中算法的骨架,而將一些步驟延遲到子類(lèi)中。模板方法使得子類(lèi)的可以不改變一個(gè)算法的結(jié)構(gòu)即可重新定義該算法的某些特定步驟。
結(jié)構(gòu)圖
使用原則:當(dāng)不變的和可變的行為在方法的子類(lèi)實(shí)現(xiàn)中混合在一起的時(shí)候,不變的行為就會(huì)在子類(lèi)中重復(fù)出現(xiàn)。我們通過(guò)模板方法模式把這些行為搬移到單一的地方,這樣就幫助子類(lèi)擺脫重復(fù)的不變行為的糾纏。
11.迪米特法則:
12.外觀模式結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
class SubSystemOne{ public void MethodOne() { Console.WriteLine(" 子系統(tǒng)方法一"); }}class SubSystemTwo{ public void MethodTwo() { Console.WriteLine(" 子系統(tǒng)方法二"); } } class SubSystemThree { public void MethodThree() { Console.WriteLine(" 子系統(tǒng)方法三"); } } class SubSystemFour { public void MethodFour() { Console.WriteLine(" 子系統(tǒng)方法四"); } }
外觀類(lèi)
客戶(hù)端
何時(shí)使用外觀模式:
首先,在設(shè)計(jì)初期階段,應(yīng)該要有意識(shí)的將不同的兩個(gè)層分離,比如經(jīng)典的三層架構(gòu),就需要考慮在數(shù)據(jù)訪問(wèn)層和業(yè)務(wù)邏輯層、業(yè)務(wù)邏輯層和表示層的層與層之間建立外觀Facade,這樣可以為復(fù)雜的子系統(tǒng)提供一個(gè)簡(jiǎn)單的接口,使得耦合大大降低。其次,在開(kāi)發(fā)階段,子系統(tǒng)往往因?yàn)椴粩嗟闹貥?gòu)演化而變得越來(lái)越復(fù)雜,大多數(shù)的模式使用時(shí)也都會(huì)產(chǎn)生很多很小的類(lèi),這本是好事,但也給外部調(diào)用它們的用戶(hù)程序帶來(lái)了使用上的困難,增加外觀Facade可以提供一個(gè)簡(jiǎn)單的接口,減少它們之間的依賴(lài)。第三,在維護(hù)一個(gè)遺留的大型系統(tǒng)時(shí),可能這個(gè)系統(tǒng)已經(jīng)非常難以維護(hù)和擴(kuò)展了,但因?yàn)樗浅V匾墓δ埽碌男枨箝_(kāi)發(fā)必須要依賴(lài)于它。此時(shí)用外觀模式Facade也是非常合適的。你可以為新系統(tǒng)開(kāi)發(fā)一個(gè)外觀Facade類(lèi),來(lái)提供設(shè)計(jì)粗糙或高度復(fù)雜的遺留代碼的比較清晰簡(jiǎn)單的接口,讓新系統(tǒng)與Facade對(duì)象交互,F(xiàn)acade與遺留代碼交互所有復(fù)雜的工作。13.建造者模式如果你需要將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過(guò)程可以創(chuàng)建不同的表示的意圖時(shí),我們需要應(yīng)用于一個(gè)設(shè)計(jì)模式,‘建造者(Builder)模式’,又叫生成器模式。如果我們用了建造者模式,那么用戶(hù)就只需指定需要建造的類(lèi)型就可以得到它們,而具體建造的過(guò)程和細(xì)節(jié)就不需知道了。它主要是用于創(chuàng)建一些復(fù)雜的對(duì)象,這些對(duì)象內(nèi)部構(gòu)建間的建造順序通常是穩(wěn)定的,但對(duì)象內(nèi)部的構(gòu)建通常面臨著復(fù)雜的變化。
以下以建造小人做示例:
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(); }//我們需要建造一個(gè)瘦的小人,則讓這個(gè)瘦子類(lèi)去繼承這個(gè)抽象類(lèi)。當(dāng)然,胖人或高個(gè)子其實(shí)都是用類(lèi)似的代碼去實(shí)現(xiàn)這個(gè)類(lèi)就可以了class PersonThinBuilder : PersonBuilder { public PersonThinBuilder(Graphics g,Pen p) : ba(g,p){ } public override void BuildHead() { g.DrawEllip(p,50,20,30,30); } public override void BuildBody() { g.DrawRectangle(p,60,50,10,50); } public override void BuildArmLeft() { g.DrawLine(p,60,50,40,100); } public override void BuildArmRight() { g.DrawLine(p,70,50,90,100); } public override void BuildLegLeft() { g.DrawLine(p,60,100,45,150); } public override void BuildLegRight() { g.DrawLine(p,70,100,85,150); } }
指揮者
客戶(hù)端
Pen p=new Pen(Color.Yellow); PersonThinBuilder ptb = new PersonThinBuilder(pictureBox1.CreateGraphics(),p); PersonDirector pdThin = new PersonDirector(ptb); pdThin.CreatePerson(); PersonFatBuilder pfb = new PersonFatBuilder(pictureBox2.CreateGraphics(),p); PersonDirector pdFat = new PersonDirector(pfb); pdFat.CreatePerson();
示例結(jié)構(gòu)圖
建造者模式結(jié)構(gòu)圖
14.觀察者模式-又叫做發(fā)布-訂閱(Publish/Subscribe)模式結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
//Subject類(lèi),可翻譯為主題或抽象通知者,一般用一個(gè)抽象類(lèi)或者一個(gè)接口實(shí)現(xiàn)。它把所有對(duì)觀察者對(duì)象的引用保存在一個(gè)聚集里,每個(gè)主題都可以有任何數(shù)量的觀察者。抽象主題提供一個(gè)接口,可以增加和刪除觀察者對(duì)象。abstract class Subject { private IList<Obrver> obrvers = new List<Obrver>(); //增加觀察者 public void Attach(Obrver obrver) { obrvers.Add(obrver); } //移除觀察者 public void Detach(Obrver obrver) { obrvers.Remove(obrver); } //通知 public void Notify() { foreach(Obrver o in obrvers) { o.Update(); } } }//ConcreteSubject類(lèi),叫做具體主題或具體通知者,將有關(guān)狀態(tài)存入具體觀察者對(duì)象;在具體主題的內(nèi)部狀態(tài)改變時(shí),給所有登記過(guò)的觀察者發(fā)出通知。具體主題角色通常用一個(gè)具體子類(lèi)實(shí)現(xiàn)。class ConcreteSubject : Subject { private string subjectState; //具體被觀察者狀態(tài) public string SubjectState { get { return subjectState; } t { subjectState = value; } } }//Obrver類(lèi),抽象觀察者,為所有的具體觀察者定義一個(gè)接口,在得到主題的通知時(shí)更新自己。abstract class Obrver{ public abstract void Update();}//ConcreteObrver類(lèi),具體觀察者,實(shí)現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài)相協(xié)調(diào)。具體觀察者角色可以保存一個(gè)指向具體主題對(duì)象的引用。具體觀察者角色通常用一個(gè)具體子類(lèi)實(shí)現(xiàn)。class ConcreteObrver : Obrver { private string name; private string obrverState; private ConcreteSubject subject; public ConcreteObrver(ConcreteSubject subject,string name) { this.subject = subject; this.name = name; } public override void Update() { obrverState = subject.SubjectState; Console.WriteLine("觀察者{0}的新?tīng)顟B(tài)是{1}",name,obrverState); } public ConcreteSubject Subject { get { return subject; } t { subject = value; } } }//客戶(hù)端代碼static void Main(string[] args) { ConcreteSubject s = new ConcreteSubject(); s.Attach(new ConcreteObrver(s,"X")); s.Attach(new ConcreteObrver(s,"Y")); s.Attach(new ConcreteObrver(s,"Z")); s.SubjectState = "ABC"; s.Notify(); Console.Read(); }//結(jié)果展示 觀察者X的新?tīng)顟B(tài)是ABC 觀察者Y的新?tīng)顟B(tài)是ABC 觀察者Z的新?tīng)顟B(tài)是ABC
用觀察者模式的動(dòng)機(jī)是什么呢?
將一個(gè)系統(tǒng)分割成一系列相互協(xié)作的類(lèi)有一個(gè)很不好的副作用,那就是需要維護(hù)相關(guān)對(duì)象間的一致性。我們不希望為了維持一致性而使各類(lèi)緊密耦合,這樣會(huì)給維護(hù)、擴(kuò)展和重用都帶來(lái)不便[DP]。而觀察者模式的關(guān)鍵對(duì)象是主題Subject和觀察者Obrver,一個(gè)Subject可以有任意數(shù)目的依賴(lài)它的Obrver,一旦Subject的狀態(tài)發(fā)生了改變,所有的Obrver都可以得到通知。Subject發(fā)出通知時(shí)并不需要知道誰(shuí)是它的觀察者,也就是說(shuō),具體觀察者是誰(shuí),它根本不需要知道。而任何一個(gè)具體觀察者不知道也不需要知道其他觀察者的存在。
什么時(shí)候考慮使用觀察者模式呢?
當(dāng)一個(gè)對(duì)象的改變需要同時(shí)改變其他對(duì)象的時(shí)候。而且它不知道具體有多少對(duì)象有待改變時(shí),應(yīng)該考慮使用觀察者模式。
總的來(lái)講,觀察者模式所做的工作其實(shí)就是在解除耦合。讓耦合的雙方都依賴(lài)于抽象,而不是依賴(lài)于具體。從而使得各自的變化都不會(huì)影響另一邊的變化。
觀察者模式的不足:有些觀察者可能不是自己寫(xiě)的代碼,可能是別人的類(lèi)庫(kù),這種情況下的觀察者是不可能實(shí)現(xiàn)Obrver接口的,這時(shí)可以通過(guò)事件委托實(shí)現(xiàn)。即在通知者里定義一個(gè)事件,然后將觀察者的更新方法添加到通知者的事件里即可。
15.抽象工廠模式以數(shù)據(jù)庫(kù)選擇為示例的結(jié)構(gòu)圖
以數(shù)據(jù)庫(kù)選擇為示例的代碼實(shí)現(xiàn)
interface IDepartment{ void Inrt(Department department); Department GetDepartment(int id);}class SqlrverDepartment : IDepartment { public void Inrt(Department department) { Console.WriteLine("在SQL Server中給Department表增加一條記錄"); } public Department GetDepartment(int id) { Console.WriteLine("在SQL Server中根據(jù)ID得到Department表一條記錄"); return null; } }class AccessDepartment : IDepartment { public void Inrt(Department department) { Console.WriteLine("在Access中給Department表增加一條記錄"); } public Department GetDepartment(int id) { Console.WriteLine("在Access中根據(jù)ID得到Department表一條記錄"); return null; } }
客戶(hù)端
、
抽象工廠模式的優(yōu)點(diǎn)
最大的好處便是易于交換產(chǎn)品系列,由于具體工廠類(lèi),例如IFactory factory = new AccessFactory(),在一個(gè)應(yīng)用中只需要在初始化的時(shí)候出現(xiàn)一次,這就使得改變一個(gè)應(yīng)用的具體工廠變得非常容易,它只需要改變具體工廠即可使用不同的產(chǎn)品配置。
第二大好處是,它讓具體的創(chuàng)建實(shí)例過(guò)程與客戶(hù)端分離,客戶(hù)端是通過(guò)它們的抽象接口操縱實(shí)例,產(chǎn)品的具體類(lèi)名也被具體工廠的實(shí)現(xiàn)分離,不會(huì)出現(xiàn)在客戶(hù)代碼中。例如上面的例子,客戶(hù)端所認(rèn)識(shí)的只有IUr和IDepartment,至于它是用SQL Server來(lái)實(shí)現(xiàn)還是Access來(lái)實(shí)現(xiàn)就不知道了。
抽象工廠模式的缺點(diǎn)
抽象工廠模式可以很方便地切換兩個(gè)數(shù)據(jù)庫(kù)訪問(wèn)的代碼,但是如果你的需求來(lái)自增加功能,比如我們現(xiàn)在要增加項(xiàng)目表Project,那就至少要增加三個(gè)類(lèi),IProject、SqlrverProject、AccessProject,還需要更改IFactory、SqlrverFactory和AccessFactory才可以完全實(shí)現(xiàn)。
用簡(jiǎn)單工廠改進(jìn)抽象工廠
拋棄了IFactory、SqlrverFactory和AccessFactory三個(gè)工廠類(lèi),取而代之的是DataAccess類(lèi),由于事先設(shè)置了db的值(Sqlrver或Access),所以簡(jiǎn)單工廠的方法都不需要輸入?yún)?shù),這樣在客戶(hù)端就只需要DataAccess.CreateUr()和DataAccess.CreateDepartment()來(lái)生成具體的數(shù)據(jù)庫(kù)訪問(wèn)類(lèi)實(shí)例,客戶(hù)端沒(méi)有出現(xiàn)任何一個(gè)SQL Server或Access的字樣,達(dá)到了解耦的目的。
結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
這個(gè)改進(jìn)還不是很優(yōu)秀,如果我需要增加Oracle數(shù)據(jù)庫(kù)訪問(wèn),本來(lái)抽象工廠只增加一個(gè)OracleFactory工廠類(lèi)就可以了,現(xiàn)在就比較麻煩了,就需要在DataAccess類(lèi)中每個(gè)方法的swicth中加ca了。
用反射+抽象工廠(更好的方式,還可以利用依賴(lài)注入)
從這個(gè)角度上說(shuō),所有在用簡(jiǎn)單工廠的地方,都可以考慮用反射技術(shù)來(lái)去除switch或if,解除分支判斷帶來(lái)的耦合。
16.狀態(tài)模式狀態(tài)模式主要解決的是當(dāng)控制一個(gè)對(duì)象狀態(tài)轉(zhuǎn)換的條件表達(dá)式過(guò)于復(fù)雜時(shí)的情況。把狀態(tài)的判斷邏輯轉(zhuǎn)移到表示不同狀態(tài)的一系列類(lèi)當(dāng)中,可以把復(fù)雜的判斷邏輯簡(jiǎn)化。當(dāng)然,如果這個(gè)狀態(tài)判斷很簡(jiǎn)單,那就沒(méi)必要用‘狀態(tài)模式’了。
結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
abstract class State
{
public abstract void Handle(Context context);
}
狀態(tài)模式消除龐大的條件分支語(yǔ)句,大的分支判斷會(huì)使得它們難以修改和擴(kuò)展,就像我們最早說(shuō)的刻版印刷一樣,任何改動(dòng)和變化都是致命的。狀態(tài)模式通過(guò)把各種狀態(tài)轉(zhuǎn)移邏輯分布到State的子類(lèi)之間,來(lái)減少相互間的依賴(lài),好比把整個(gè)版面改成了一個(gè)又一個(gè)的活字,此時(shí)就容易維護(hù)和擴(kuò)展了。
什么時(shí)候使用狀態(tài)模式?
當(dāng)一個(gè)對(duì)象的行為取決于它的狀態(tài),并且它必須在運(yùn)行時(shí)刻根據(jù)狀態(tài)改變它的行為時(shí),就可以考慮使用狀態(tài)模式了。另外如果業(yè)務(wù)需求某項(xiàng)業(yè)務(wù)有多個(gè)狀態(tài),通常都是一些枚舉常量,狀態(tài)的變化都是依靠大量的多分支判斷語(yǔ)句來(lái)實(shí)現(xiàn),此時(shí)應(yīng)該考慮將每一種業(yè)務(wù)狀態(tài)定義為一個(gè)State的子類(lèi)。這樣這些對(duì)象就可以不依賴(lài)于其他對(duì)象而獨(dú)立變化了,某一天客戶(hù)需要更改需求,增加或減少業(yè)務(wù)狀態(tài)或改變狀態(tài)流程,對(duì)你來(lái)說(shuō)都是不困難的事。
17.適配器模式適配器模式主要應(yīng)用于希望復(fù)用一些現(xiàn)存的類(lèi),但是接口又與復(fù)用環(huán)境要求不一致的情況。
注意:在GoF的設(shè)計(jì)模式中,對(duì)適配器模式講了兩種類(lèi)型,類(lèi)適配器模式和對(duì)象適配器模式,由于類(lèi)適配器模式通過(guò)多重繼承對(duì)一個(gè)接口與另一個(gè)接口進(jìn)行匹配,而C#、VB.NET、JAVA等語(yǔ)言都不支持多重繼承(C++支持),也就是一個(gè)類(lèi)只有一個(gè)父類(lèi),所以我們這里主要講的是對(duì)象適配器。
結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
class Target
{
public virtual void Request()
{
Console.WriteLine("普通請(qǐng)求!");
}
}
class Adaptee
{
public void SpecificRequest()
{
Console.WriteLine("特殊請(qǐng)求!");
}
}
何時(shí)使用適配器模式
兩個(gè)類(lèi)所做的事情相同或相似,但是具有不同的接口時(shí)要使用它。客戶(hù)代碼可以統(tǒng)一調(diào)用同一接口就行了,這樣應(yīng)該可以更簡(jiǎn)單、更直接、更緊湊。
其實(shí)用適配器模式也是無(wú)奈之舉,很有點(diǎn)‘亡羊補(bǔ)牢’的感覺(jué),沒(méi)辦法呀,是軟件就有維護(hù)的一天,維護(hù)就有可能會(huì)因不同的開(kāi)發(fā)人員、不同的產(chǎn)品、不同的廠家而造成功能類(lèi)似而接口不同的情況,此時(shí)就是適配器模式大展拳腳的時(shí)候了。即我們通常是在軟件開(kāi)發(fā)后期或維護(hù)期再考慮使用它。
那有沒(méi)有設(shè)計(jì)之初就需要考慮用適配器模式的時(shí)候?
當(dāng)然有,比如公司設(shè)計(jì)一系統(tǒng)時(shí)考慮使用第三方開(kāi)發(fā)組件,而這個(gè)組件的接口與我們自己的系統(tǒng)接口是不相同的,而我們也完全沒(méi)有必要為了迎合它而改動(dòng)自己的接口,此時(shí)盡管是在開(kāi)發(fā)的設(shè)計(jì)階段,也是可以考慮用適配器模式來(lái)解決接口不同的問(wèn)題。
適配器模式在.Net的應(yīng)用
在.NET中有一個(gè)類(lèi)庫(kù)已經(jīng)實(shí)現(xiàn)的、非常重要的適配器,那就是DataAdapter。DataAdapter用作DataSet和數(shù)據(jù)源之間的適配器以便檢索和保存數(shù)據(jù)。DataAdapter通過(guò)映射Fill(這更改了DataSet中的數(shù)據(jù)以便與數(shù)據(jù)源中的數(shù)據(jù)相匹配)和Update(這更改了數(shù)據(jù)源中的數(shù)據(jù)以便與DataSet中的數(shù)據(jù)相匹配)來(lái)提供這一適配器[MSDN]。由于數(shù)據(jù)源可能是來(lái)自SQL Server,可能來(lái)自O(shè)racle,也可能來(lái)自Access、DB2,這些數(shù)據(jù)在組織上可能有不同之處,但我們希望得到統(tǒng)一的DataSet(實(shí)質(zhì)是XML數(shù)據(jù)),此時(shí)用DataAdapter就是非常好的手段,我們不必關(guān)注不同數(shù)據(jù)庫(kù)的數(shù)據(jù)細(xì)節(jié),就可以靈活的使用數(shù)據(jù)。
適配器模式和代理模式的區(qū)別
適配器模式和代理模式看似類(lèi)似,其實(shí)他們是不同的,代理模式的兩個(gè)類(lèi)是繼承相同接口的,只是在一些場(chǎng)景下需要用代理去訪問(wèn)真實(shí)對(duì)象。而適配器里的兩個(gè)類(lèi)是功能相似,但是具有不同的接口。
18.備忘錄模式結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
備忘錄模式的應(yīng)用場(chǎng)合:
Memento模式比較適用于功能比較復(fù)雜的,但需要維護(hù)或記錄屬性歷史的類(lèi),或者需要保存的屬性只是眾多屬性中的一小部分時(shí),Originator可以根據(jù)保存的Memento信息還原到前一狀態(tài)。例如當(dāng)對(duì)象的狀態(tài)改變的時(shí)候,有可能這個(gè)狀態(tài)無(wú)效,這時(shí)候就可以使用暫時(shí)存儲(chǔ)起來(lái)的備忘錄將狀態(tài)復(fù)原。
19.組合模式結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
何時(shí)使用組合模式
當(dāng)你發(fā)現(xiàn)需求中是體現(xiàn)部分與整體層次的結(jié)構(gòu)時(shí),以及你希望用戶(hù)可以忽略組合對(duì)象與單個(gè)對(duì)象的不同,統(tǒng)一地使用組合結(jié)構(gòu)中的所有對(duì)象時(shí),就應(yīng)該考慮用組合模式了。例如以前曾經(jīng)用過(guò)的ASP.NET的TreeView控件就是典型的組合模式應(yīng)用。還例如我們寫(xiě)過(guò)的自定義控件,也就是把一些基本的控件組合起來(lái),通過(guò)編程寫(xiě)成一個(gè)定制的控件,比如用兩個(gè)文本框和一個(gè)按鈕就可以寫(xiě)一下自定義的登錄框控件,實(shí)際上,所有的Web控件的基類(lèi)都是System.Web.UI.Control,而Control基類(lèi)中就有Add和Remove方法,這就是典型的組合模式的應(yīng)用。
20.迭代器模式本來(lái)這個(gè)模式還是有點(diǎn)意思的,不過(guò)現(xiàn)今來(lái)看迭代器模式實(shí)用價(jià)值遠(yuǎn)不如學(xué)習(xí)價(jià)值大了,MartinFlower甚至在自己的網(wǎng)站上提出撤銷(xiāo)此模式。因?yàn)楝F(xiàn)在高級(jí)編程語(yǔ)言如C#、JAVA等本身已經(jīng)把這個(gè)模式做在語(yǔ)言中了。
結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
當(dāng)如果只有一種遍歷方式時(shí),可以直接定義一個(gè)ConcreteIterator而不需要實(shí)現(xiàn)抽象的Iterator,但是如果有多重遍歷方式時(shí),定義抽象的抽象的Iterator就很必要。例如上面的遍歷是從頭到尾,現(xiàn)在實(shí)現(xiàn)一個(gè)從尾到頭的遍歷:
這時(shí)你客戶(hù)端只需要更改一個(gè)地方就可以實(shí)現(xiàn)反向遍歷了:
//Iterator i = new ConcreteIterator(a);
Iterator i = new ConcreteIteratorDesc(a);
.NET的迭代器實(shí)現(xiàn)
IEumerator支持對(duì)非泛型集合的簡(jiǎn)單迭代接口
IEnumerable公開(kāi)枚舉數(shù),該枚舉數(shù)支持在非泛型集合上進(jìn)行簡(jiǎn)單迭代
你會(huì)發(fā)現(xiàn),這兩個(gè)接口,特別是IEumerator要比我們剛才寫(xiě)的抽象類(lèi)Iterator要簡(jiǎn)潔,但可實(shí)現(xiàn)的功能卻一點(diǎn)不少,這其實(shí)也是對(duì)GoF的設(shè)計(jì)改良的結(jié)果。有了這個(gè)基礎(chǔ),你再來(lái)看你最熟悉的foreach in就很簡(jiǎn)單了:
這里用到了foreach in而在編譯器里做了些什么呢?其實(shí)它做的是下面的工作
IEnumerator<string> e = a.GetEnumerator();
while(e.MoveNext())
{
Console.WriteLine("{0} 請(qǐng)買(mǎi)車(chē)票!",e.Current);
}
21.單例模式通常我們可以讓一個(gè)全局變量使得一個(gè)對(duì)象被訪問(wèn),但它不能防止你實(shí)例化多個(gè)對(duì)象。一個(gè)最好的辦法就是,讓類(lèi)自身負(fù)責(zé)保存它的唯一實(shí)例。這個(gè)類(lèi)可以保證沒(méi)有其他實(shí)例可以被創(chuàng)建,并且它可以提供一個(gè)訪問(wèn)該實(shí)例的方法。
結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
單例模式和類(lèi)似.Net框架里的Math類(lèi)什么區(qū)別:
它們之間的確很類(lèi)似,實(shí)用類(lèi)通常也會(huì)采用私有化的構(gòu)造方法來(lái)避免其有實(shí)例。但它們還是有很多不同的,比如實(shí)用類(lèi)不保存狀態(tài),僅提供一些靜態(tài)方法或靜態(tài)屬性讓你使用,而單例類(lèi)是有狀態(tài)的。實(shí)用類(lèi)不能用于繼承多態(tài),而單例雖然實(shí)例唯一,卻是可以有子類(lèi)來(lái)繼承。實(shí)用類(lèi)只不過(guò)是一些方法屬性的集合,而單例卻是有著唯一的對(duì)象實(shí)例。在運(yùn)用中還得仔細(xì)分析再作決定用哪一種方式。
多線程的單例
多線程的程序中,多個(gè)線程同時(shí),注意是同時(shí)訪問(wèn)Singleton類(lèi),調(diào)用GetInstance()方法,會(huì)有可能造成創(chuàng)建多個(gè)實(shí)例的。這是可以通過(guò)加鎖來(lái)處理。
上面這個(gè)方式每次調(diào)用GetInstance方法時(shí)都需要lock,不太好,下面是優(yōu)化寫(xiě)法(也叫雙重鎖定)
靜態(tài)初始化
C#與公共語(yǔ)言運(yùn)行庫(kù)也提供了一種‘靜態(tài)初始化’方法,這種方法不需要開(kāi)發(fā)人員顯式地編寫(xiě)線程安全代碼,即可解決多線程環(huán)境下它是不安全的問(wèn)題。
由于這種靜態(tài)初始化的方式是在自己被加載時(shí)就將自己實(shí)例化,所以被形象地稱(chēng)之為餓漢式單例類(lèi),原先的單例模式處理方式是要在第一次被引用時(shí),才會(huì)將自己實(shí)例化,所以就被稱(chēng)為懶漢式單例類(lèi)。
22.合成/聚合原則合成/聚合復(fù)用原則的好處是,優(yōu)先使用對(duì)象的合成/聚合將有助于你保持每個(gè)類(lèi)被封裝,并被集中在單個(gè)任務(wù)上。這樣類(lèi)和類(lèi)繼承層次會(huì)保持較小規(guī)模,并且不太可能增長(zhǎng)為不可控制的龐然大。例如下面的結(jié)構(gòu)圖就是用錯(cuò)了繼承:
改成用合成/聚合后:
23.橋接模式‘將抽象部分與它的實(shí)現(xiàn)部分分離’,還是不好理解,我的理解就是實(shí)現(xiàn)系統(tǒng)可能有多角度分類(lèi),每一種分類(lèi)都有可能變化,那么就把這種多角度分離出來(lái)讓它們獨(dú)立變化,減少它們之間的耦合。
也就是說(shuō),在發(fā)現(xiàn)我們需要多角度去分類(lèi)實(shí)現(xiàn)對(duì)象,而只用繼承會(huì)造成大量的類(lèi)增加,不能滿(mǎn)足開(kāi)放-封閉原則時(shí),就應(yīng)該要考慮用橋接模式了。
結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
abstract class Implementor { public abstract void Operation(); }class ConcreteImplementorA : Implementor { public override void Operation() { Console.WriteLine("具體實(shí)現(xiàn)A的方法執(zhí)行"); } }class ConcreteImplementorB : Implementor { public override void Operation() { Console.WriteLine("具體實(shí)現(xiàn)B的方法執(zhí)行"); } }class Abstraction{ protected Implementor implementor; public void SetImplementor(Implementor implementor) { this.implementor = implementor; } public virtual void Operation() { implementor.Operation(); } }class RefinedAbstraction : Abstraction { public override void Operation() { implementor.Operation(); } }static void Main(string[] args){ Abstraction ab = new RefinedAbstraction(); ab.SetImplementor(new ConcreteImplementorA()); ab.Operation(); ab.SetImplementor(new ConcreteImplementorB()); ab.Operation(); Console.Read(); }24.命令模式
結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
abstract class Command{ protected Receiver receiver; public Command(Receiver receiver) { this.receiver = receiver; } abstract public void Execute(); }class ConcreteCommand : Command{ public ConcreteCommand(Receiver receiver) : ba(receiver) { } public override void Execute() { receiver.Action(); } }class Invoker{ private Command command; public void SetCommand(Command command) { this.command = command; } public void ExecuteCommand() { command.Execute(); }}class Receiver{ public void Action() { Console.WriteLine("執(zhí)行請(qǐng)求!"); }}static void Main(string[] args) { Receiver r = new Receiver(); Command c = new ConcreteCommand(r); Invoker i = new Invoker(); i.SetCommand(c); i.ExecuteCommand(); Console.Read(); }
命令模式的優(yōu)點(diǎn):
第一,它能較容易地設(shè)計(jì)一個(gè)命令隊(duì)列;
第二,在需要的情況下,可以較容易地將命令記入日志;
第三,允許接收請(qǐng)求的一方?jīng)Q定是否要否決請(qǐng)求;
第四,可以容易地實(shí)現(xiàn)對(duì)請(qǐng)求的撤銷(xiāo)和重做;
第五,由于加進(jìn)新的具體命令類(lèi)不影響其他的類(lèi),因此增加新的具體命令類(lèi)很容易;
其實(shí)還有最關(guān)鍵的優(yōu)點(diǎn)就是命令模式把請(qǐng)求一個(gè)操作的對(duì)象與知道怎么執(zhí)行一個(gè)操作的對(duì)象分割開(kāi)。
敏捷開(kāi)發(fā)原則告訴我們,不要為代碼添加基于猜測(cè)的、實(shí)際不需要的功能。如果不清楚一個(gè)系統(tǒng)是否需要命令模式,一般就不要著急去實(shí)現(xiàn)它,事實(shí)上,在需要的時(shí)候通過(guò)重構(gòu)實(shí)現(xiàn)這個(gè)模式并不困難,只有在真正需要如撤銷(xiāo)/恢復(fù)操作等功能時(shí),把原來(lái)的代碼重構(gòu)為命令模式才有意義。
25.職責(zé)鏈模式結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
這當(dāng)中最關(guān)鍵的是當(dāng)客戶(hù)提交一個(gè)請(qǐng)求時(shí),請(qǐng)求是沿鏈傳遞直至有一個(gè)ConcreteHandler對(duì)象負(fù)責(zé)處理它。接收者和發(fā)送者都沒(méi)有對(duì)方的明確信息,且鏈中的對(duì)象自己也并不知道鏈的結(jié)構(gòu)。結(jié)果是職責(zé)鏈可簡(jiǎn)化對(duì)象的相互連接,它們僅需保持一個(gè)指向其后繼者的引用,而不需保持它所有的候選接受者的引用。由于是在客戶(hù)端來(lái)定義鏈的結(jié)構(gòu),也就是說(shuō),我可以隨時(shí)地增加或修改處理一個(gè)請(qǐng)求的結(jié)構(gòu)。增強(qiáng)了給對(duì)象指派職責(zé)的靈活性。
職責(zé)鏈模式和撞他模式區(qū)別
狀態(tài)模式與職責(zé)鏈模式的最大的不同是設(shè)置自己的下一級(jí)的問(wèn)題上,狀態(tài)模式是在類(lèi)的設(shè)計(jì)階段就定好的,不能在客戶(hù)端改變,而職責(zé)鏈的下一級(jí)是在客戶(hù)端自己來(lái)確定的。
26.中介者模式結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
class ConcreteColleague1 : Colleague
{
public ConcreteColleague1(Mediator mediator): ba(mediator) { }
public void Send(string message)
{
mediator.Send(message,this);
由于有了Mediator,使得ConcreteColleague1和ConcreteColleague2在發(fā)送消息和接收信息時(shí)其實(shí)是通過(guò)中介者來(lái)完成的,這就減少了它們之間的耦合度了。
中介者模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):中介者模式的優(yōu)點(diǎn)首先是Mediator的出現(xiàn)減少了各個(gè)Colleague的耦合,使得可以獨(dú)立地改變和復(fù)用各個(gè)Colleague類(lèi)和Mediator。由于把對(duì)象如何協(xié)作進(jìn)行了抽象,將中介作為一個(gè)獨(dú)立的概念并將其封裝在一個(gè)對(duì)象中,這樣關(guān)注的對(duì)象就從對(duì)象各自本身的行為轉(zhuǎn)移到它們之間的交互上來(lái),也就是站在一個(gè)更宏觀的角度去看待系統(tǒng)。
缺點(diǎn):由于ConcreteMediator控制了集中化,于是就把交互復(fù)雜性變?yōu)榱酥薪檎叩膹?fù)雜性,這就使得中介者會(huì)變得比任何一個(gè)ConcreteColleague都復(fù)雜。
中介者模式的應(yīng)用
用.NET寫(xiě)的Windows應(yīng)用程序中的Form或Web網(wǎng)站程序的aspx就是典型的中介者。
比如下面用winform開(kāi)發(fā)的計(jì)算器程序,它上面有菜單控件、文本控件、多個(gè)按鈕控件和一個(gè)Form窗體,每個(gè)控件之間的通信都是通過(guò)誰(shuí)來(lái)完成的?它們之間是否知道對(duì)方的存在?
由于每個(gè)控件的類(lèi)代碼都被封裝了,所以它們的實(shí)例是不會(huì)知道其他控件對(duì)象的存在的,比如點(diǎn)擊數(shù)字按鈕要在文本框中顯示數(shù)字,按照我以前的想法就應(yīng)該要在Button類(lèi)中編寫(xiě)給TextBox類(lèi)實(shí)例的Text屬性賦值的代碼,造成兩個(gè)類(lèi)有耦合,這顯然是非常不合理的。但實(shí)際情況是它們都有事件機(jī)制,而事件的執(zhí)行都是在Form窗體的代碼中完成,也就是說(shuō)所有的控件的交互都是由Form窗體來(lái)作中介,操作各個(gè)對(duì)象,這的確是典型的中介者模式應(yīng)用。
27.享元模式結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
abstract class Flyweight{ public abstract void Operation(int extrinsicstate);}class ConcreteFlyweight : Flyweight{ public override void Operation(int extrinsicstate) { Console.WriteLine("具體Flyweight:"+extrinsicstate); }}class UnsharedConcreteFlyweight : Flyweight{ public override void Operation(int extrinsicstate) { Console.WriteLine("不共享的具體Flyweight:" + extrinsicstate); }}
內(nèi)部狀態(tài)與外部狀態(tài)
享元模式可以避免大量非常相似類(lèi)的開(kāi)銷(xiāo)。在程序設(shè)計(jì)中,有時(shí)需要生成大量細(xì)粒度的類(lèi)實(shí)例來(lái)表示數(shù)據(jù)。如果能發(fā)現(xiàn)這些實(shí)例除了幾個(gè)參數(shù)外基本上都是相同的,有時(shí)就能夠受大幅度地減少需要實(shí)例化的類(lèi)的數(shù)量。如果能把那些參數(shù)移到類(lèi)實(shí)例的外面,在方法調(diào)用時(shí)將它們傳遞進(jìn)來(lái),就可以通過(guò)共享大幅度地減少單個(gè)實(shí)例的數(shù)目。也就是說(shuō),享元模式Flyweight執(zhí)行時(shí)所需的狀態(tài)是有內(nèi)部的也可能有外部的,內(nèi)部狀態(tài)存儲(chǔ)于ConcreteFlyweight對(duì)象之中,而外部對(duì)象則應(yīng)該考慮由客戶(hù)端對(duì)象存儲(chǔ)或計(jì)算,當(dāng)調(diào)用Flyweight對(duì)象的操作時(shí),將該狀態(tài)傳遞給它。
享元模式應(yīng)用
如果一個(gè)應(yīng)用程序使用了大量的對(duì)象,而大量的這些對(duì)象造成了很大的存儲(chǔ)開(kāi)銷(xiāo)時(shí)就應(yīng)該考慮使用;還有就是對(duì)象的大多數(shù)狀態(tài)可以外部狀態(tài),如果刪除對(duì)象的外部狀態(tài),那么可以用相對(duì)較少的共享對(duì)象取代很多組對(duì)象,此時(shí)可以考慮使用享元模式。因?yàn)橛昧讼碓J剑杂辛斯蚕韺?duì)象,實(shí)例總數(shù)就大大減少了,如果共享的對(duì)象越多,存儲(chǔ)節(jié)約也就越多,節(jié)約量隨著共享狀態(tài)的增多而增大。
實(shí)際上在.NET中,字符串string就是運(yùn)用了Flyweight模式。舉個(gè)例子吧。Object.ReferenceEquals(object objA,object objB)方法是用來(lái)確定objA與objB是否是相同的實(shí)例,返回值為bool值。
string titleA = "大話(huà)設(shè)計(jì)模式";
string titleB = "大話(huà)設(shè)計(jì)模式";
Console.WriteLine(Object.ReferenceEquals(titleA,titleB));
返回值竟然是True,這兩個(gè)字符串是相同的實(shí)例,試想一下,如果每次創(chuàng)建字符串對(duì)象時(shí),都需要?jiǎng)?chuàng)建一個(gè)新的字符串對(duì)象的話(huà),內(nèi)存的開(kāi)銷(xiāo)會(huì)很大。所以如果第一次創(chuàng)建了字符串對(duì)象titleA,下次再創(chuàng)建相同的字符串titleB時(shí)只是把它的引用指向‘大話(huà)設(shè)計(jì)模式’,這樣就實(shí)現(xiàn)了‘大話(huà)設(shè)計(jì)模式’在內(nèi)存中的共享。
再比如說(shuō)休閑游戲開(kāi)發(fā)中,像圍棋、五子棋、跳棋等,它們都有大量的棋子對(duì)象,你分析一下,它們的內(nèi)部狀態(tài)和外部狀態(tài)各是什么?
圍棋和五子棋只有黑白兩色、跳棋顏色略多一些,但也是不太變化的,所以顏色應(yīng)該是棋子的內(nèi)部狀態(tài),而各個(gè)棋子之間的差別主要就是位置的不同,所以方位坐標(biāo)應(yīng)該是棋子的外部狀態(tài)。像圍棋,一盤(pán)棋理論上有361個(gè)空位可以放棋子,那如果用常規(guī)的面向?qū)ο蠓绞骄幊蹋勘P(pán)棋都可能有兩三百個(gè)棋子對(duì)象產(chǎn)生,一臺(tái)服務(wù)器就很難支持更多的玩家玩圍棋游戲了,畢竟內(nèi)存空間還是有限的。如果用了享元模式來(lái)處理棋子,那么棋子對(duì)象可以減少到只有兩個(gè)實(shí)例。
28.解釋器模式解釋器模式需要解決的是,如果一種特定類(lèi)型的問(wèn)題發(fā)生的頻率足夠高,那么可能就值得將該問(wèn)題的各個(gè)實(shí)例表述為一個(gè)簡(jiǎn)單語(yǔ)言中的句子。這樣就可以構(gòu)建一個(gè)解釋器,該解釋器通過(guò)解釋這些句子來(lái)解決該問(wèn)題。
例如我們經(jīng)常使用的正則表達(dá)式就是解釋器模式的應(yīng)用,因?yàn)檫@個(gè)匹配字符的需求在軟件的很多地方都會(huì)使用,而且行為之間都非常類(lèi)似,過(guò)去的做法是針對(duì)特定的需求,編寫(xiě)特定的函數(shù),比如判斷Email、匹配電話(huà)號(hào)碼等等,與其為每一個(gè)特定需求都寫(xiě)一個(gè)算法函數(shù),不如使用一種通用的搜索算法來(lái)解釋執(zhí)行一個(gè)正則表達(dá)式,該正則表達(dá)式定義了待匹配字符串的集合。解釋器為正則表達(dá)式定義了一個(gè)文法,如何表示一個(gè)特定的正則表達(dá)式,以及如何解釋這個(gè)正則表達(dá)式。
結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
abstract class AbstractExpression{ public abstract void Interpret(Context context); }class TerminalExpression : AbstractExpression { public override void Interpret(Context context) { Console.WriteLine("終端解釋器"); } }class NonterminalExpression : AbstractExpression{ public override void Interpret(Context context) { Console.WriteLine("非終端解釋器"); } }class Context{ private string input; public string Input { get { return input; } t { input = value; } } private string output; public string Output { get { return output; } t { output = value; } } }static void Main(string[] args) { Context context = new Context(); IList<AbstractExpression> list = new List<AbstractExpression>(); list.Add(new TerminalExpression()); list.Add(new NonterminalExpression()); list.Add(new TerminalExpression()); list.Add(new TerminalExpression()); foreach(AbstractExpression exp in list) { exp.Interpret(context); } Console.Read(); }
解釋器模式看起來(lái)好像不難,但其實(shí)真正做起來(lái)應(yīng)該還是很難的,就如同你開(kāi)發(fā)了一個(gè)編程語(yǔ)言或腳本給自己或別人用。解釋器模式就是用‘迷你語(yǔ)言’來(lái)表現(xiàn)程序要解決的問(wèn)題,以迷你語(yǔ)言寫(xiě)成‘迷你程序’來(lái)表現(xiàn)具體的問(wèn)題。通常當(dāng)有一個(gè)語(yǔ)言需要解釋執(zhí)行,并且你可將該語(yǔ)言中的句子表示為一個(gè)抽象語(yǔ)法樹(shù)時(shí),可使用解釋器模式。
用了解釋器模式,就意味著可以很容易地改變和擴(kuò)展文法,因?yàn)樵撃J绞褂妙?lèi)來(lái)表示文法規(guī)則,你可使用繼承來(lái)改變或擴(kuò)展該文法。也比較容易實(shí)現(xiàn)文法,因?yàn)槎x抽象語(yǔ)法樹(shù)中各個(gè)節(jié)點(diǎn)的類(lèi)的實(shí)現(xiàn)大體類(lèi)似,這些類(lèi)都易于直接編寫(xiě)。
解釋器模式也有不足的,解釋器模式為文法中的每一條規(guī)則至少定義了一個(gè)類(lèi),因此包含許多規(guī)則的文法可能難以管理和維護(hù)。建議當(dāng)文法非常復(fù)雜時(shí),使用其他的技術(shù)如語(yǔ)法分析程序或編譯器生成器來(lái)處理。
29.訪問(wèn)者模式下面以成功或者失敗時(shí),男人和女人的反應(yīng)為例畫(huà)出的結(jié)構(gòu)圖
代碼實(shí)現(xiàn)
//成功
class Success : Action
{
public override void GetManConclusion(Man concreteElementA)
{
Console.WriteLine("{0}{1}時(shí),背后多半有一個(gè)偉大的女人。",concreteElementA.GetType().Name,this.GetType().Name);
}
public override void GetWomanConclusion(Woman concreteElementB)
{
Console.WriteLine("{0}{1}時(shí),背后大多有一個(gè)不成功的男人。",concreteElementB.GetType().Name,this.GetType().Name);
}
}
//失敗
class Failing : Action
{
//與上面代碼類(lèi)同,省略
}
//戀愛(ài)
class Amativeness : Action
{
//與上面代碼類(lèi)同,省略
}
這個(gè)模式使用有個(gè)前提:如果人類(lèi)的性別不止是男和女,而是可有多種性別,那就意味‘狀態(tài)’類(lèi)中的抽象方法就不可能穩(wěn)定了,每加一種類(lèi)別,就需要在狀態(tài)類(lèi)和它的所有下屬類(lèi)中都增加一個(gè)方法,這就不符合開(kāi)放-封閉原則。也就是說(shuō),訪問(wèn)者模式適用于數(shù)據(jù)結(jié)構(gòu)相對(duì)穩(wěn)定的系統(tǒng)。
真正結(jié)構(gòu)圖
訪問(wèn)者模式的目的是要把處理從數(shù)據(jù)結(jié)構(gòu)分離出來(lái)。很多系統(tǒng)可以按照算法和數(shù)據(jù)結(jié)構(gòu)分開(kāi),如果這樣的系統(tǒng)有比較穩(wěn)定的數(shù)據(jù)結(jié)構(gòu),又有易于變化的算法的話(huà),使用訪問(wèn)者模式就是比較合適的,因?yàn)樵L問(wèn)者模式使得算法操作的增加變得容易。反之,如果這樣的系統(tǒng)的數(shù)據(jù)結(jié)構(gòu)對(duì)象易于變化,經(jīng)常要有新的數(shù)據(jù)對(duì)象增加進(jìn)來(lái),就不適合使用訪問(wèn)者模式。事實(shí)上,我們很難找到數(shù)據(jù)結(jié)構(gòu)不變化的情況,所以用訪問(wèn)者模式的機(jī)會(huì)也就不太多了。
本文發(fā)布于:2023-02-28 20:57:00,感謝您對(duì)本站的認(rèn)可!
本文鏈接:http://m.newhan.cn/zhishi/a/167771064994669.html
版權(quán)聲明:本站內(nèi)容均來(lái)自互聯(lián)網(wǎng),僅供演示用,請(qǐng)勿用于商業(yè)和其他非法用途。如果侵犯了您的權(quán)益請(qǐng)與我們聯(lián)系,我們將在24小時(shí)內(nèi)刪除。
本文word下載地址:vb treeview(vb treeview 展開(kāi)).doc
本文 PDF 下載地址:vb treeview(vb treeview 展開(kāi)).pdf
| 留言與評(píng)論(共有 0 條評(píng)論) |