Template Method模板:
動機:在軟體構建過程中,對於某一項任務,它常常有穩定的整體操作結構,但各個子步驟卻有很多改變需求,或者由於故有的原因(比如架構與應用之間)而無法和任務的整體結構同時實現。
Template Method意圖:定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中,Template Method使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。
Template Method要點:
1.Template Method模式一種非常基礎性的設計模式,在物件導向系統中有著大量的應用,它用最簡潔的機制(虛函數的多態性)為很多應用程式架構提供了靈活的擴充點,是代碼複用方面的基本實現結構。
2.除了可以用靈活應對子步驟的變化外,“不要調用我 ,讓我調用你”的反向控制結構是Template Method的典型應用。
3.在具體實現方面,被Template Method調用的虛方法可具有實現,也可以沒有任何實現(抽象方法,純虛方法),但一般推薦將它們設定為protected方法。
Command 命令:
耦合是軟體不能抵禦變化災難的根本性原因。不僅實體物件與實體物件之間存在耦合關係(建立型模式解決),實體物件與行為操作之間也存在耦合關係。
Command意圖:將一個請求封裝為一個對象,從而使你可用不同的請求對客戶進行參數化,對請求排隊或者記錄請求日誌以及支援可撤銷的操作。
一般做法:
class App
{
public void Show()
{
Document doc=new Document();
doc.ShowText();
Graphcis graph=new Graphics();
graph.ShowGraphics();
}
}
class Document
{
public void ShowText(){...}
}
class Graphics
{
public void ShowGraphics(){....}
}
而現在:
public interface Command
{
public void Show();
public void Undo();
public void Redo();
}
class Document:Command
{
public virtual void Show(){....}
}
class Graphics:Command
{
public virtual void Show(){....}
}
class App
{
Stack<Command> stack;
public void Show()
{
foreach(Command c in stack){c.show();}
}
//以下為Redo Undo操作,這樣的操作在這個模式中表現充分。
public void Undo()
{Command command=stack.pop();command.Undo();Undolist.Add(command);}
public void Redo()
{Command command=undolist.pop();command.redo();}
}
事實上,標準的Command模式應該如下:
前提:Document類和Graphcis類已經有了。
現在,
//實現Command模式
public interface Command
{
public void Show();
public void Undo();
public void Redo();
}
//具體化的命令對象--從抽象意義來講,DocumentCommand表示一個行為。
class DocumentCommand:Command
{show中調用doc的ShowText。}
class GraphicsCommand:Command
{show中調用graphics的ShowGraphics}
class App與上面的類似。
Interpreter解譯器:
例子:五億三百七十五萬六千四百五十二--->轉換為503756452.
在軟體構建過程中,如果某一特定領域的問題比較複雜,類似的模式不斷重複出現,如果使用普通的編程方式來實現將面臨非常頻繁的變化。
在這種情況下,將特定領域的問題表達為某種文法規則下的句子,然後構建一個解譯器來解釋這樣的句子,從而達到解決問題的目的。
Interpreter意圖:給定一個語言,定義它的文法的一種表示,並定義一種解譯器,這個解譯器使用該表示來解釋語言中的句子。
Interpreter要點:
1.Interpreter模式的應用場合是Interpreter模式應用中痛點,只有滿足“商務規則頻繁變化,且類似的模式不斷重複出現,並且容易抽象為文法規則的問題”才適合使用Interpreter模式。
2.使用Interpreter模式來表示文法規則,從而可以用物件導向技巧來方便地“擴充”文法。
3.Interpreter模式比較適合簡單的文法表示,對於複雜的文法表示,Interpreter模式會產生比較大的階層,需要求助於文法分析產生器這樣的標準工具。
Mediator中介者:
在軟體構建過程中,經常會出現多一個對象互相關聯互動的情況,對象之間常常會維持一種複雜的參考關聯性,如果遇到一些需求的更改,這種直接的參考關聯性將面臨不斷的變化。
在這種情況下,我們可使用一個“中介對象”來管理對象間的管理關聯性,避免相互互動的對象之間的耦合參考關聯性,從而更好地抵禦變化。
Mediator意圖:用一個中介對象來封裝一系列的對象互動。中介者使各對象不需要顯式的相互應用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。
用中介者來通知相關的對象。
Mediator模式要點:
1.將多個對象之間複雜的關聯關係解耦,Mediator模式將多個對象間的控制邏輯進行集中管理,變“多個對象互相管理”為“多個對象和一個中介者管理”,簡化系統的維護,抵禦了可能的變化。
2.隨著控制邏輯的變化,Mediator具體對象的實現可能相當複雜,這時可以對Mediator對象進行分解處理。
3.Facade模式是解耦系統外到系統內(單向)的對象關聯關係,Mediator模式是解耦系統內各個對象之間(雙向)的關聯關係。
Iterator迭代:
很多語言(C++,C#,Java)中,其實已經實現了。
在軟體構建過程中,集合對象內部結構常常變化各異,但對於這些集合對象,我們希望在不暴露其內部結構的同時,可以讓外部客戶代碼透明地訪問其中包含的元素,同時這種“透明遍曆”也為“同一種演算法在多種集合對象上進行操作”提供了可能。
使用物件導向技術將這種遍曆機制抽象為“迭代器對象”為“應對變化中的集合對象”提供了一種優雅的方法。
Iterator意圖:
提供一種方法順序訪問一個彙總對象中的各個元素,卻又不暴露該對象的內部表示。
Iterator要點:
1.迭代抽象:訪問一個彙總對象的內容而五需暴露他的內部表示。
2.迭代多態:為遍曆不同的集合結構提供一個統一的介面,從而支援同樣的演算法在不同集合結構上進行操作。
3.迭代器的健壯性考慮:遍曆的同時更改迭代器所在的結合結構,會導致問題。
Observer觀察者:
在軟體構建過程中,我們需要為某些對象建立一種“同時依賴關係”---一個對象(目標對象)的狀態發生改變,所有的依賴對象(觀察者對象)都將得到通知。
Observer意圖:
定義對象間的一種一對多的依賴關係,以便當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並自動更新。
常規做法:
public class Bank
{
A a; //為通知A。B 聲明一個欄位。這就是強依賴關係。
B b;
public void Withdraw() //取款
{a.qukuanA();b.qukuanB();}
}
public class A
{
public void qukuanA(){取款}
}
public class B
{
public void qukuanB(){取款}
}
現在,應該為A,B抽象出一個抽象類別或者介面。
public interface IObserver
{
void Update(); //然後A,B繼承這個介面並進行重寫
}
//然後在App中用一個集合類,App現在就只依賴上面的介面了。在App中可以進行foreach處理,當然可以增或刪被通知的對象。
Observer要點:
1.使用物件導向的抽象,Observer模式使得我們可以獨立地改變目標與觀察者,從而使二者之間的依賴關係達到松耦合。
2.目標發送通知時,無需指定觀察者,通知(可以攜帶通知資訊作為參數)會自動傳播。觀察者自己決定是否需要訂閱通知,目標對象對此一無所知。
3.C#的event中,委託充當了抽象的Observer介面,而提供事件的對象充當了目標對象。委託是比抽象Observer介面更為松耦合的設計。
Chain of Responsibility職責鏈(用的不多):
一個請求可能被多個對象處理,但是每個請求在運行時只能有一個接受者,如果顯式指定,將必不可少地帶來請求寄件者與接受者的緊耦合。
如何使請求的寄件者不需要指定具體的接受者?讓請求的接受者自己在運行時決定來處理請求,從而使兩者解耦。
Chain of Responsibility意圖:
使多個對象都有機會處理請求,從而避免請求的寄件者和接收者之間的耦合關係。將這些對象連成一條鏈,並沿著這條鏈傳遞請求,直到有一個對象處理它為止。
Chain of Responsibility要點:
1.Chain of Responsibility模式的應用場合在於“一個請求可能有多個接受者,但是最後真正的接受者只有一個”,只有這時候請求寄件者與接受者的耦合才有可能出現“變化脆弱”的癥狀,職責鏈的目的就是將二者解耦,從而更好地應對變化
2.應用了Chain of Responsibility模式後,對象的職責指派將更具靈活性,我們可以在運行時動態添加、修改請求的處理職責。
3.如果請求傳遞到職責鏈的末尾仍得不到處理,應該有一個合理的預設機制,這也是每一個接受對象的責任,而不是發出請求的對象的責任。
Memento備忘錄:
對象狀態的變化無端,如何回溯、回複對象在某個點的裝?
在軟體構建過程中,某些對象的狀態在轉換過程中,可能由於某種需要,要求程式能夠回溯到對象之前處於某個點時的狀態,如果使用一些公有介面來讓其他對象得到對象的狀態,便會暴露對象的細節實現。
Memento意圖:
在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外儲存這個狀態,這樣以後就可以將該對象恢複到原先儲存的狀態。
如何?對象狀態的良好儲存與恢複?
但同時又不會因此而破壞對象本身的封裝性。
例子:
常規做法:
class Rectangle
{
int x,y; //座標
int height,width;
//可能有的操作
Moveto(){....};
ChangeHeight(){...};
//用於儲存狀態的操作
SetValue(rec);
}
客戶類:
class App
{
Rectangle rec= new Rectangle(0,0,10,20);
Rectangle rSaved= new Rectangle(0,0,10,20);//用於儲存狀態
process(){首先應該是rec複製給rSaved,然後進行Moveto,changge等操作}
//儲存操作
Save(){rSaved.SetValue();}
}
但上面有一個問題:rSaved本來是用來保持狀態的,它不應該具有Moveto,change等操作的許可權。
我們就有一個想法:把rSaved做成一個只有儲存狀態的操作,別的一概沒有。實現如下:
新增加一個類:
class RectangleMemento
{
int x,y,height,width;
SetState(int x,int y,int height,int width)
{this.x=x;.......}
}
然後在Rectangle類中增加一個方法:
class Rectangle
{
int x,y; //座標
int height,width;
//可能有的操作
Moveto(){....};
ChangeHeight(){...};
//用於儲存狀態的操作
SetValue(rec);
RectangleMemento CreateMemento() //增加的方法
{RectangleMemento rm=new RectangleMemento();
rm.SetState(this.x,this,y...);
return rm;}
void SetMemento() //增加的恢複方法
{this=x=rm.x;....}
}
Memento模式要點:
1.Memento儲存原發器對象的內部狀態,在需要時恢複原發器狀態,Memento模式適用於“由原發器管理,卻又必須儲存在原發器之外的資訊”
2.在實現Memento模式中,要防止原發器以外的對象訪問備忘錄對象,備忘錄對象有兩個介面,一個為原發器使用的寬介面,一個為其他對象使用的窄介面
3.在實現Memento模式時,要考慮拷貝對象狀態的效率問題,如果對象開銷比較大,可以採用某種增量式改變來改進Memento模式。
State狀態:
對象狀態影響對象行為。
某些對象的狀態如果改變,其行為也會隨之而發生變化,比如文檔處於唯讀狀態,其支援的行為和讀寫狀態支援的行為就可能完全不同。
如何在運行時根據對象的狀態來透明地更改對象的行為?而不會為對象操作和狀態轉化之間引入緊耦合?
State意圖:
允許一個對象在其內部狀態改變時改變它的行為,從而使對象看起來似乎修改了其行為。
State要點:
1.State模式將所有與一個特定狀態相關的行為都放入一個State的子類對象中,在對象狀態切換時,切換相應的對象,但同時維持State的介面,這樣實現了具體的操作與狀態轉化之間的解耦。
2.為不同的狀態引入不同的對象使得狀態轉換變得更加明確,而且可以保證不會出現狀態不一致情況,因為轉換是原子性的,即要麼徹底轉換過來,要麼不轉換。
3.如果State對象沒有執行個體變數,那麼各個上下文可以共用同一個State對象,從而節省對象開銷。
Strategy策略:
對象可能經常需要使用多種不同的演算法,但是如果變化頻繁,會將類型變得脆弱。
某些對象使用的演算法可能多種多樣,經常改變,如果將這些演算法都編碼到對象中,將會使對象變得異常複雜,而且有時候支援不使用的演算法也是一個效能負擔。
如何在運行時根據需要透明地更改對象的演算法?
將演算法與對象本身解耦,從而避免上述問題?
Strategy意圖:
定義一系列演算法,把它們一個個封裝起來,並且使他們可相互替換,該模式使得演算法可獨立於使用它的客戶而變化。
Strategy要點:
1.Strategy及其子類為組件提供了一系列可重用的演算法,從而可以使得類型在運行時方便地根據需要在各個演算法之間進行切換,所謂封裝演算法,支援演算法的變化。
2.Strategy模式提供了用條件判斷語句以外的另一種選擇,消除條件判斷語句,就是在解耦合,含有許多條件判斷語句的代碼通常都需要Strategy模式。
3.與State類似,如果Strategy對象沒有執行個體變數,那麼各個上下文可以共用同一個Strategy對象,從而節省對象開銷。
Visitor訪問者:
例子:一個抽象Shape類型,有虛方法Draw,Shape有子類Rectangle,Circle,Line,如果要給Shape類型加一個操作Move,那將在其三個子類中分別加如這個操作。所以抽象出一個Visitor類,讓它複雜增加操作。
類階層中可能經常引入新的操作,從而將類型變得脆弱。
由於需求的改變,某些類結構中常常需要增加新的方法,如果在基類中做這樣的更改,將會給子類帶來很繁重的變更負擔,甚至破壞原有設計。
如果在不更改類階層的前提下,在運行時根據需要透明地為類階層上的各個類動態添加操作,從而避免上述問題?
Visitor意圖:
表示一個作用於某種對象結構中的各元素的操作,它可以在不改變各元素的類的前提下定義作用於這些元素的新的操作。
Visitor要點:
1.Visitor模式通過所謂雙重分發(double dispatch)來實現在不更改Element類階層的前提下,在運行時透明地為類階層上的各個類動態添加新的操作。
2.所屬雙重分發即Visitor模式中間包括了兩個多態分發(注意其中的多態機制):第一個為accept方法的多態辨析;第二個為visit方法的多態辨析。
3.Visitor模式的最大缺點在於擴充類階層(增添新的Element子類),會導致Visitor類的改變,因此Visitor模式適用於“Element類階層穩定,而其中的操作卻經常面臨頻繁改動”。
行為型設計模式總結:
Template Method模式封裝演算法結構,支援演算法子步驟變化。
Strategy模式注重封裝演算法,支援演算法的變化
State模式注重封裝與狀態相關的行為,支援狀態的變化。
Memento模式注重封裝對象狀態變化,支援狀態儲存、回複
Mediator模式注重封裝對象間的互動,支援對象互動的變化。
Chain of Responsibility模式注重封裝對象責任,支援責任的變化。
Command模式注重將請求封裝為對象,支援要求的變化。
Iterator模式注重封裝集合對象內部結構,支援集合的變化,(同Composite,大多數語言已實現)
Interpreter模式注重封裝特定領域變化,支援領域問題的頻繁變化。
Observer模式注重封裝對象通知,支援通訊對象的變化。
Visitor模式注重封裝對象操作變化,支援在運行時為類階層動態添加新的操作。
最後:
1.設計模式建立在對系統變化點的基礎上進行,哪裡有變化點,哪裡有應用設計模式。
2.設計模式應該以演化的方式來獲得,系統的變化點往往是經過不斷演化才能準確定位。
3.不能為了模式而模式,設計模式是一種軟體設計的軟力量,而非規範標準,不應誇大設計模式的作用。