前段時(shí)間擎场,在自己糊里糊涂地寫了一年多的代碼之后羽德,接手了一坨一個(gè)同事的代碼。身邊很多人包括我自己都在痛罵那些亂糟糟毫無設(shè)計(jì)可言的代碼迅办,我不禁開始深思:自己真的比他高明很多嗎宅静?我可以很自信地承認(rèn),在代碼風(fēng)格和單元測(cè)試上可以完勝站欺,可是設(shè)計(jì)模式呢姨夹?自己平時(shí)開始一個(gè)project的時(shí)候有認(rèn)真考慮過設(shè)計(jì)模式嗎?答案是并沒有矾策,我甚至都數(shù)不出有哪些設(shè)計(jì)模式磷账。于是,我就拿起了這本設(shè)計(jì)模式黑皮書蝴韭。
中文版《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》够颠,譯自英文版《Design Patterns: Elements of Reusable Object-Oriented Software》。原書由Erich Gamma, Richard Helm, Ralph Johnson 和John Vlissides合著榄鉴。這幾位作者常被稱為“Gang of Four”履磨,即GoF蛉抓。該書列舉了23種主要的設(shè)計(jì)模式,因此剃诅,其他地方經(jīng)常提到的23種GoF設(shè)計(jì)模式巷送,就是指本書中提到的這23種設(shè)計(jì)模式。
把書看完很容易矛辕,但是要理解透徹笑跛,融匯貫通很難,能夠在實(shí)際中靈活地選擇合適的設(shè)計(jì)模式運(yùn)用起來就更是難上加難了聊品。所以飞蹂,我打算按照本書的組織結(jié)構(gòu)(把23種設(shè)計(jì)模式分成三大類)寫三篇讀書筆記,一來自我總結(jié)翻屈,二來備忘供以后自己翻閱陈哑。與此同時(shí),如果能讓讀者有一定的收獲就更棒了伸眶。我覺得本書的前言有句話很對(duì)惊窖,“第一次閱讀此書時(shí)你可能不會(huì)完全理解它,但不必著急厘贼,我們?cè)谄鸪蹙帉戇@本書時(shí)也沒有完全理解它們界酒!請(qǐng)記住,這不是一本讀完一遍就可以束之高閣的書嘴秸。我們希望你在軟件設(shè)計(jì)過程中反復(fù)參閱此書毁欣,以獲取設(shè)計(jì)靈感”。
本節(jié)將介紹行為模式岳掐,包括職責(zé)鏈模式署辉、命令模式、解釋器模式岩四、迭代器模式、中介者模式哥攘、備忘錄模式剖煌、觀察者模式、狀態(tài)模式逝淹、策略模式耕姊、模板方法模式和訪問者模式等十一種模式。行為模式涉及到算法和對(duì)象間職責(zé)的分配栅葡。行為模式不僅描述對(duì)象或類的模式茉兰,還描述它們之間的通信模式。這些模式刻畫了在運(yùn)行時(shí)難以跟蹤的復(fù)雜的控制流欣簇,它們將你的注意力從控制流轉(zhuǎn)移到對(duì)象間的聯(lián)系方式上來规脸。行為類模式使用繼承機(jī)制在類間分派行為坯约,如模板方法和解釋器。行為對(duì)象模式則使用對(duì)象復(fù)合而不是繼承莫鸭,一些行為對(duì)象模式描述了一組對(duì)等的對(duì)象怎樣相互協(xié)作以完成其中任一對(duì)象都無法單獨(dú)完成的任務(wù)闹丐,如職責(zé)鏈、中介者和觀察者被因。其他的行為對(duì)象模式常將行為封裝在一個(gè)對(duì)象中并將請(qǐng)求指派給它卿拴,如策略模式、命令模式梨与、狀態(tài)模式堕花、訪問者模式和迭代器模式。
1. 職責(zé)鏈模式(Chain of responsibility)
職責(zé)鏈模式粥鞋,使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求缘挽,從而避免請(qǐng)求的發(fā)送者和接收者之間的耦合關(guān)系。將這些對(duì)象連成一條鏈陷虎,并沿著這條鏈傳遞該請(qǐng)求到踏,直到有一個(gè)對(duì)象處理它為止。
動(dòng)機(jī):
考慮一個(gè)圖形用戶界面中的上下文有關(guān)的幫助機(jī)制尚猿。用戶在界面的任一部分上點(diǎn)擊都可以得到幫助信息窝稿,幫助信息依賴于點(diǎn)擊的界面上下文,如果該部分沒有幫助信息凿掂,則顯示一個(gè)關(guān)于當(dāng)前上下文的比較一般的幫助信息伴榔。很顯然應(yīng)該按照從特殊到最普遍的順序來組織幫助信息,而且提交幫助請(qǐng)求的對(duì)象并不明確知道誰是最終提供幫助的對(duì)象庄萎。職責(zé)鏈模式的想法是踪少,給多個(gè)對(duì)象處理一個(gè)請(qǐng)求的機(jī)會(huì),從而解耦合發(fā)送者和接受者糠涛。該請(qǐng)求沿對(duì)象鏈傳遞援奢,鏈中收到請(qǐng)求的對(duì)象要么親自處理它,要么轉(zhuǎn)發(fā)給鏈中的下一個(gè)候選者忍捡,直至其中一個(gè)對(duì)象處理它集漾。
UML結(jié)構(gòu)圖:
代碼示例:
下面是動(dòng)機(jī)那一節(jié)中提到的提供幫助信息的簡(jiǎn)單示例。
public class HelpHandler
{
private HelpHandler successor;
public HelpHandler(HelpHandler helpHandler)
{
this.successor = helpHandler;
}
public virtual void HandleHelp()
{
if(successor != null)
{
successor->HandleHelp();
}
}
}
public class Widget : HelpHandler
{
private Widget parent;
public bool HasHelp()
{
//Check if we have help
}
public Widget(parent) : HelpHandler(parent)
{
}
}
public class Button : Widget
{
public Button(Widget button) : Widget(button)
{
}
public override void HandleHelp()
{
if(this.HasHelp())
{
//offer help on the button
} else {
this.HandleHelp();
}
}
}
適用情況:
(1) 有多個(gè)的對(duì)象可以處理一個(gè)請(qǐng)求砸脊,哪個(gè)對(duì)象處理該請(qǐng)求運(yùn)行時(shí)刻自動(dòng)確定具篇;
(2) 你想在不明確指定接收者的情況下,向多個(gè)對(duì)象中的一個(gè)提交一個(gè)請(qǐng)求凌埂;
(3) 可處理一個(gè)請(qǐng)求的對(duì)象結(jié)合應(yīng)被動(dòng)態(tài)指定驱显。
2. 命令模式(Command)
命令模式,將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而使你可用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化埃疫。如伏恐,對(duì)請(qǐng)求進(jìn)行排隊(duì)、記錄請(qǐng)求日志熔恢,以及支持可撤銷的操作脐湾。
動(dòng)機(jī):
有時(shí)必須向某對(duì)象提交請(qǐng)求,但并不知道關(guān)于被請(qǐng)求的操作或者請(qǐng)求的接受者的任何消息叙淌。例如秤掌,用戶界面工具箱包括按鈕和菜單這樣的對(duì)象,它們執(zhí)行請(qǐng)求響應(yīng)用戶輸入鹰霍。但工具箱不能顯示地在按鈕或菜單中實(shí)現(xiàn)該請(qǐng)求闻鉴,因?yàn)橹挥惺褂霉ぞ呦涞膽?yīng)用知道該由哪個(gè)對(duì)象做哪個(gè)操作。而工具箱的設(shè)計(jì)者無法知道請(qǐng)求的接受者或執(zhí)行的操作茂洒。命令模式通過將請(qǐng)求本身變成一個(gè)對(duì)象來使工具箱對(duì)象可向未指定的應(yīng)用對(duì)象提出請(qǐng)求孟岛。這一模式的關(guān)鍵在于抽象的Command類,它定義了一個(gè)執(zhí)行操作的接口督勺,如Execute操作渠羞。
UML結(jié)構(gòu)圖:
代碼示例:
下面是一個(gè)簡(jiǎn)單的Command模式的示例,我們實(shí)現(xiàn)了開關(guān)的“從開到關(guān)”和“從關(guān)到開”轉(zhuǎn)換的command類智哀。
public interface ICommand
{
void Execute();
}
public interface ISwitchable
{
void PowerOn();
void PowerOff();
}
public class CloseSwitchCommand : ICommand
{
private ISwitchable switchable;
public CloseSwitchCommand(ISwitchable switchable)
{
this.switchable = switchable;
}
public void Execute()
{
this.switchable.PowerOn();
}
}
public class OpenSwitchCommand : ICommand
{
private ISwitchable switchable;
public OpenSwitchCommand(ISwitchable switchable)
{
this.switchable = switchable;
}
public void Execute()
{
this.switchable.PowerOff();
}
}
public class Switch
{
ICommand closedCommand;
ICommand openedCommand;
public Switch(ICommand closedCommand, ICommand openedCommand)
{
this.closedCommand = closedCommand;
this.openedCommand = openedCommand;
}
public void Close()
{
this.closedCommand.Execute();
}
public void Open()
{
this.openedCommand.Execute();
}
}
適用情況:
(1) 抽象出待執(zhí)行的動(dòng)作以參數(shù)化某對(duì)象次询;
(2) 在不同的時(shí)刻指定、排列和執(zhí)行請(qǐng)求瓷叫;
(3) 支持取消操作屯吊;
(4) 支持修改日志,這樣系統(tǒng)崩潰時(shí)摹菠,這些修改可以被重做一遍盒卸;
(5) 用構(gòu)建在原語操作上的高層操作構(gòu)造一個(gè)系統(tǒng)。
3. 解釋器模式(Interpreter)
解釋器模式次氨,給定一個(gè)語言蔽介,定義它的文法表示和一個(gè)解釋器,這個(gè)解釋器使用所定義的文法表示來解釋語言中的句子煮寡。
動(dòng)機(jī):
例如屉佳,對(duì)于搜索匹配一個(gè)模式的字符串問題,正則表達(dá)式是描述字符串模式的一種標(biāo)準(zhǔn)語言洲押。與其為每一個(gè)模式都構(gòu)造一個(gè)特定的算法,不如使用一種通用的搜索算法來解釋執(zhí)行一個(gè)正則表達(dá)式圆凰,該正則表達(dá)式定義了待匹配字符串的集合杈帐。解釋器模式描述了如何為簡(jiǎn)單的語言定義一個(gè)文法,如何在該語言中表示一個(gè)句子,以及如何解釋這些句子挑童。
UML結(jié)構(gòu)圖:
代碼示例:
下面是一個(gè)只包含TerminalExpression和NonterminalExpression的解釋器模式的簡(jiǎn)單框架累铅。
public class Context
{
}
abstract class AbstractExpression
{
public abstract void Interpret(Context context);
}
public class TerminalExpression : AbstractExpression
{
public override void Interpret(Context context)
{
// Interpret for terminal expression
}
}
public class NonterminalExpression : AbstractExpression
{
public override void Interpret(Context context)
{
// Interpret for nonterminal expression
}
}
適用情況:
當(dāng)有一個(gè)語言需要解釋執(zhí)行,并且你可將該語言中的句子表示為一個(gè)抽象語法樹時(shí)站叼,可使用解釋器模式娃兽。
(1) 文法簡(jiǎn)單。對(duì)于復(fù)雜的文法尽楔,文法的類層次變得龐大而無法管理投储;
(2) 效率不是一個(gè)關(guān)鍵問題。最高效的解釋器通常不是通過直接解釋語法分析樹實(shí)現(xiàn)的阔馋,而是首將它們轉(zhuǎn)換成另一種形式玛荞。
4. 迭代器模式(Iterator)
迭代器模式,提供一種方法順序訪問一個(gè)聚合對(duì)象中各個(gè)元素呕寝,而又不需暴露該對(duì)象的內(nèi)部表示勋眯。
動(dòng)機(jī):
一個(gè)聚合對(duì)象,如列表下梢,應(yīng)該提供一種方法來讓別人可以訪問它的元素客蹋,而又不需要暴露它的內(nèi)部結(jié)構(gòu)。此外孽江,針對(duì)不同的需要讶坯,可能要以不同的方式遍歷這個(gè)聚合對(duì)象。迭代器模式的關(guān)鍵思想是竟坛,將對(duì)列表的訪問和遍歷從列表對(duì)象中分離出來并放入一個(gè)迭代器對(duì)象中闽巩。迭代器類定義了一個(gè)訪問該列表元素的接口,迭代器對(duì)象負(fù)責(zé)跟蹤當(dāng)前的元素担汤。
UML結(jié)構(gòu)圖
代碼示例:
下面一個(gè)簡(jiǎn)單的例子實(shí)現(xiàn)了一個(gè)Iterator來訪問Item集合涎跨。
public class Item
{
private string name;
public Item(string name)
{
this.name = name;
}
public string Name => this.name;
}
public interface IAbstractCollection
{
Iterator CreateIterator();
}
public class Collection : IAbstractCollection
{
private ArrayList items = new ArrayList();
public Iterator CreateIterator()
{
return new Iterator(this);
}
public int Count => items.Count;
public object this[int index]
{
get {return items[index];}
set {items.Add(value);}
}
}
public interface IAbstractIterator
{
Item First();
Item Next();
bool IsDone {get;}
Item CurrentItem {get;}
}
public class Iterator : IAbstractIterator
{
private Collection collection;
private int current = 0;
public Iterator(Collection collection)
{
this.collection = collection;
}
public Item First()
{
current = 0;
return collection[current] as Item;
}
public Item Next()
{
++current;
if(IsDone)
return null;
else
return collection[current] as Item;
}
public Item CurrentItem
{
get {return collection[current] as Item;}
}
public bool IsDone
{
get {return current >= collection.Count;}
}
}
適用情況:
(1) 訪問一個(gè)聚合對(duì)象的內(nèi)容而無需暴露它的內(nèi)部表示;
(2) 支持對(duì)聚合對(duì)象的多種遍歷崭歧;
(3) 為遍歷不同的聚合結(jié)構(gòu)提供一個(gè)統(tǒng)一的接口(即支持多態(tài)迭代)隅很。
5. 中介者模式(Mediator)
中介者模式,用一個(gè)中介對(duì)象來封裝一系列的對(duì)象交互率碾。中介者使各個(gè)對(duì)象不需要顯示地相互作用叔营,從而使其耦合松散,而且可以獨(dú)立地改變它們之間的交互所宰。
動(dòng)機(jī):
面向?qū)ο笤O(shè)計(jì)鼓勵(lì)將行為分布到各個(gè)對(duì)象中绒尊,這種分布可能會(huì)導(dǎo)致對(duì)象間有許多連接。大量的相互連接使得一個(gè)對(duì)象似乎不太可能在沒有其他對(duì)象的支持下工作仔粥∮て祝可以通過將集體行為封裝在一個(gè)單獨(dú)的中介者對(duì)象中避免這個(gè)問題蟹但。中介者負(fù)責(zé)控制和協(xié)調(diào)一組對(duì)象間的交互,中介者充當(dāng)一個(gè)中介以使組中的對(duì)象不再相互顯示引用谭羔。這些對(duì)象僅知道中介者华糖,從而減少了相互連接的數(shù)目。
UML結(jié)構(gòu)圖:
代碼示例:
下面這個(gè)簡(jiǎn)單的中介者的例子瘟裸,所謂的中介者就是由它來與Component1和Component2來交互客叉,使得它們之間是松耦合的。
public interface IComponent
{
void SetState(object state);
}
public class Component1 : IComponent
{
public void SetState(object state)
{
// Set state for component1
}
}
public class Component2 : IComponent
{
public void SetState(object state)
{
// Set state for component2
}
}
public class Mediator
{
public IComponent Component1 {get; set;}
public IComponent Component2 {get; set;}
public void ChangeState(object state)
{
this.Component1.SetState(state);
this.Component2.SetState(state);
}
}
適用情況:
(1) 一組對(duì)象以定義良好但是復(fù)雜的方式進(jìn)行通信话告,產(chǎn)生的相互依賴關(guān)系結(jié)構(gòu)混亂且難以理解兼搏;
(2) 一個(gè)對(duì)象引用其他很多對(duì)象并且直接與這些對(duì)象通信,導(dǎo)致難以復(fù)用該對(duì)象超棺;
(3) 想定制一個(gè)分布在多個(gè)類中的行為向族,而又不想生成太多的子類。
6. 備忘錄模式(Memento)
備忘錄模式棠绘,在不破壞封裝性的前提下件相,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài)瓢宦,并在該對(duì)象之外保存這個(gè)狀態(tài)疙赠。
動(dòng)機(jī):
有時(shí)有必要記錄一個(gè)對(duì)象的內(nèi)部狀態(tài)。為了允許用戶取消不確定的操作或從錯(cuò)誤中恢復(fù)過來酣难,需要實(shí)現(xiàn)檢查點(diǎn)和取消機(jī)制让虐,而實(shí)現(xiàn)這些機(jī)制紊撕,你必須事先將狀態(tài)信息保存在某處,這樣才能將對(duì)象恢復(fù)到它們先前的狀態(tài)赡突。但是對(duì)象通常封裝了其部分或所有的狀態(tài)信息对扶,使得其狀態(tài)不能被其他對(duì)象訪問。備忘錄模式就派上用場(chǎng)了惭缰,一個(gè)備忘錄是一個(gè)對(duì)象浪南,它存儲(chǔ)另一個(gè)對(duì)象在某個(gè)瞬間的內(nèi)部狀態(tài),而后者稱為備忘錄的原發(fā)器漱受。當(dāng)需要設(shè)置原發(fā)器的檢查點(diǎn)時(shí)络凿,取消機(jī)制會(huì)向原發(fā)器請(qǐng)求一個(gè)備忘錄,原發(fā)器用掃描當(dāng)前狀態(tài)的信息初始化該備忘錄昂羡。
UML結(jié)構(gòu)圖:
代碼示例:
下面是一個(gè)簡(jiǎn)單的Memento模式的例子絮记,我們?cè)贠riginator中有一個(gè)Memento對(duì)象,用來保存運(yùn)行過程中Originator的state虐先,這樣當(dāng)它需要回退時(shí)可以取回之前保存在Memento中的state怨愤。
public class State
{
}
public class Memento
{
private State state;
public Memento(State state)
{
this.state = state;
}
public State GetState()
{
return this.state;
}
public void SetState(State state)
{
this.state = state;
}
}
public class Originator
{
private Memento memento;
private State currentState;
public Originator(State state)
{
this.currentState = state;
this.memento = new Memento(state);
}
public SetMemento(Memento memento)
{
memento.SetMemento(currentState);
}
public void Revert()
{
this.currentState = memento.GetState();
}
}
適用情況:
(1) 必須保存一個(gè)對(duì)象在某個(gè)時(shí)刻的(部分)狀態(tài),這樣以后需要時(shí)它才能恢復(fù)到先前的狀態(tài)蛹批。
(2) 如果一個(gè)用接口來讓其他對(duì)象直接得到這狀態(tài)憔四,將會(huì)暴露對(duì)象的實(shí)現(xiàn)細(xì)節(jié)并破壞對(duì)象的封裝性膀息。
7. 觀察者模式(Observer)
觀察者模式,定義對(duì)象間的一種一對(duì)多的依賴關(guān)系了赵,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新甸赃。
動(dòng)機(jī):
將一個(gè)系統(tǒng)分割成一系列相互協(xié)作的類有一個(gè)常見的副作用柿汛,需要維護(hù)相關(guān)對(duì)象間的一致性。觀察者模式描述了如何建立這種相關(guān)關(guān)系埠对。這一模式的關(guān)鍵對(duì)象是目標(biāo)和觀察者络断,一個(gè)目標(biāo)可有任意數(shù)目的依賴它的觀察者。一旦目標(biāo)的狀態(tài)發(fā)生改變项玛,所有的觀察者都得到通知貌笨。
UML結(jié)構(gòu)圖:
代碼示例:
下面是一個(gè)經(jīng)典的關(guān)于觀察者模式的示例。我們有一個(gè)Heater用來加熱水襟沮,當(dāng)水溫達(dá)到一定得溫度后會(huì)觸發(fā)BoilEvent锥惋,而BoilEvent上綁定為委托都會(huì)被執(zhí)行。
public class Heater
{
private int temperature;
public delegate void BoilHandler(int param);
pubilc event BoilHandler BoilEvent;
public void BoilWater()
{
foreach(int t in Enumerable.Range(0, 101))
{
temperature = t;
if(temperature > 95)
{
if(BoilEvent 开伏!= null)
{
BoilEvent(temperature);
}
}
}
}
}
public class Alarm
{
public void MakeAlert(int param)
{
Console.WriteLine($"Alarm : the temperature of water reached {param}");
}
}
public class Display()
{
public static void ShowMessage(int param)
{
Console.WriteLine($"Display: the temperature of water reached {param}");
}
}
Usage:
Heater heater = new Heater();
heater.BoilEvent += (new Alarm()).MakeAlert;
heater.BoilEvent += Display.ShowMessage;
heater.BoilWater();
適用情況:
(1) 當(dāng)一個(gè)抽象模型有兩個(gè)方面膀跌,其中一個(gè)方面依賴于另一個(gè)方面。
(2) 當(dāng)對(duì)一個(gè)對(duì)象的改變需要同時(shí)改變其他對(duì)象固灵,而不知道具體有多少對(duì)象待改變捅伤。
(3) 當(dāng)一個(gè)對(duì)象必須通知其他對(duì)象,而它又不能假定其他對(duì)象是誰巫玻。
8. 狀態(tài)模式(State)
狀態(tài)模式丛忆,允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變時(shí)改變它的行為。對(duì)象看起來似乎修改了它的類仍秤。
動(dòng)機(jī):
考慮一個(gè)表示網(wǎng)絡(luò)連接的類TCPConnection熄诡,一個(gè)它的對(duì)象的狀態(tài)可以處于Established、Listening或Closed徒扶。當(dāng)一個(gè)TCPConnection對(duì)象接收到其他對(duì)象的請(qǐng)求時(shí)粮彤,它根據(jù)自身的當(dāng)前狀態(tài)做出不同的反應(yīng)。State模式描述了TCPConnection如何在每一種狀態(tài)下表現(xiàn)出不同的行為姜骡。這一模式的關(guān)鍵思想是引入一個(gè)稱為TCPState的抽象類來表示網(wǎng)絡(luò)的連接狀態(tài)导坟,TCPState類為各個(gè)表示不同的操作狀態(tài)的子類聲明了一個(gè)公共的接口,而它的子類則會(huì)去實(shí)現(xiàn)與特定狀態(tài)相關(guān)的行為圈澈。
UML結(jié)構(gòu)圖:
代碼示例:
下面是狀態(tài)模式的簡(jiǎn)單示例惫周。SateContext類中有一個(gè)對(duì)IState對(duì)象,動(dòng)態(tài)執(zhí)行時(shí)具體的State變化后相應(yīng)的相應(yīng)Request的方法也會(huì)變化康栈。
public interface IState
{
void Handle()
{
}
}
public class ConcreteStateA : IState
{
public void Handle()
{
// Handle for ConcreteStateA
}
}
public class ConcreteStateB : IState
{
public void Handle()
{
// Handle for ConcreteStateB
}
}
public class StateContext
{
private IState state;
public StateContext(IState state)
{
this.state = state;
}
public void Request()
{
this.state.Handle();
}
}
適用情況:
(1) 一個(gè)對(duì)象的行為取決于它的狀態(tài)递递,并且它必須在運(yùn)行時(shí)刻根據(jù)狀態(tài)改變它的行為喷橙;
(2) 一個(gè)操作中含有龐大的多分支的條件語句,且這些分支依賴于該對(duì)象的狀態(tài)登舞。
9. 策略模式(Strategy)
策略模式贰逾,定義一系列的算法,把它們一個(gè)個(gè)封裝起來菠秒,并且使它們可以相互替換疙剑。本模式使得算法可獨(dú)立于使用它的客戶而變化。
動(dòng)機(jī):
有許多算法可對(duì)一個(gè)正文流進(jìn)行分行践叠,將這些算法硬編碼進(jìn)使用它的類中是不可取的言缤。因?yàn)檫@樣做不僅使得客戶程序變得復(fù)雜難以分割,而且可能還需要支持一些我們并不會(huì)使用的換行算法禁灼。因此管挟,我們可以定義一些類來封裝不同的換行算法,從而避免這些問題弄捕。假設(shè)一個(gè)Composition類負(fù)責(zé)維護(hù)和更新一個(gè)正文瀏覽程序中顯示的正文換行僻孝。換行策略不是Composition類實(shí)現(xiàn)的,而是由抽象的Compositor類的子類(實(shí)現(xiàn)不用的換行策略)各自獨(dú)立地實(shí)現(xiàn)的察藐,Composition類只維護(hù)一個(gè)對(duì)Compositor對(duì)象的引用皮璧。
UML結(jié)構(gòu)圖:
代碼示例:
下面是一個(gè)簡(jiǎn)單的策略模式示例。我們對(duì)value1和value2會(huì)根據(jù)相應(yīng)的策略來執(zhí)行加法或者減法分飞。
public interface ICalculate
{
int Calculate(int value1, int value2);
}
public class Minus : ICalculate
{
public int Calculate(int value1, int value2)
{
return value1 - value2;
}
}
public class Plus : ICalculate
{
public int Calculate(int value1, int value2)
{
return value1 + value2;
}
}
public class CalculateClient
{
private ICalculate strategy;
public int Calculate(int value1, int value2)
{
return strategy.Calculate(value1, value2);
}
public void SetCalculate(ICalculate strategy)
{
this.strategy = strategy;
}
}
適用情況:
(1)許多相關(guān)的類僅僅是行為有異悴务。策略提供了一種用多個(gè)行為中的一個(gè)行為來配置一個(gè)類的方法;
(2)需要使用一個(gè)算法的不同變體譬猫;
(3)算法使用客戶不應(yīng)該知道的數(shù)據(jù)讯檐。這種情況下可使用策略模式以避免暴露復(fù)雜的、與算法無關(guān)的數(shù)據(jù)結(jié)構(gòu)染服。
10. 模板方法模式(Template Method)
模板方法别洪,定義一個(gè)操作中的算法的骨架,而將一些步驟延遲到子類中柳刮。模板方法使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟挖垛。
動(dòng)機(jī):
考慮一個(gè)提供Application和Document類的應(yīng)用數(shù)據(jù),Application類負(fù)責(zé)打開一個(gè)已有的外部形式存儲(chǔ)的文檔秉颗,如一個(gè)文件痢毒。一旦一個(gè)文檔中的信息從該文件中讀出后,它就由一個(gè)Document對(duì)象表示蚕甥。用框架構(gòu)建的應(yīng)用可以通過繼承Application和Document來滿足特定的需求哪替。例如,一個(gè)繪圖應(yīng)用定義DrawApplication和DrawDocument子類菇怀;一個(gè)電子表格應(yīng)用定義SpreadsheetApplication和SpreadSheetDocument子類凭舶。OpenDocument定義了一個(gè)打開文檔的每一個(gè)主要步驟(檢查該文檔是否打開晌块,創(chuàng)建相應(yīng)的Document對(duì)象,讀取等)帅霜。我們稱OpenDocument為一個(gè)模板方法匆背,它用一些抽象的操作定義一個(gè)算法,而子類將重定義這些操作提供具體的行為身冀。
UML結(jié)構(gòu)圖:
代碼示例:
下面是動(dòng)機(jī)這一節(jié)中介紹的例子的簡(jiǎn)單實(shí)現(xiàn)靠汁。
abstract class Document
{
public abstract void DoRead();
}
abstract class Application
{
public abstract bool CanOpenDocument();
public abstract Document DoCreateDocument();
public void OpenDocument()
{
if(CanOpenDocument())
{
Document document = DoCreateDocument();
document.DoRead();
}
}
}
public class DrawApplication : Application
{
public override bool CanOpenDocument()
{
}
public override Document DoCreateDocument()
{
// Return DrawDocument
}
}
public class SpreadsheetApplication : Application
{
public override bool CanOpenDocument()
{
}
public override Document DoCreateDocument()
{
// Return SpreadsheetDocument
}
}
適用情況:
(1) 一次性實(shí)現(xiàn)一個(gè)算法的不變部分,并將可變的行為留給子類來實(shí)現(xiàn)闽铐;
(2) 各個(gè)類中公共的行為應(yīng)被提取出來并集中到一個(gè)公共父類中以避免代碼重復(fù);
(3) 控制子類擴(kuò)展奶浦。
11. 訪問者模式(Visitor)
訪問者模式兄墅,表示一個(gè)作用于某對(duì)象結(jié)構(gòu)中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用于這些元素的新操作澳叉。
動(dòng)機(jī):
考慮一個(gè)編譯器隙咸,它將源程序表示為一個(gè)抽象語法樹。該編譯器需在抽象語法樹上實(shí)施某些操作以進(jìn)行“靜態(tài)語義”分析成洗,也需要生成代碼五督。因此它可能要定義許多操作以進(jìn)行類型檢查、代碼優(yōu)化瓶殃、流程分析充包,此外,還可使用抽象語法樹進(jìn)行優(yōu)美格式打印遥椿、程序重構(gòu)等等基矮。這些操作大多要求對(duì)不同的節(jié)點(diǎn)進(jìn)行不同的處理。這里有兩個(gè)問題冠场,一個(gè)是家浇,將所有這些操作分散到各種結(jié)構(gòu)點(diǎn)類中會(huì)導(dǎo)致整個(gè)系統(tǒng)難以理解、難以維護(hù)和修改碴裙。另一個(gè)是钢悲,增加新的操作通常需要重新編譯所有這些類。因此舔株,我們可以將每個(gè)類中相關(guān)的操作包裝在一個(gè)獨(dú)立的對(duì)象(稱為Visitor)中莺琳,并在遍歷抽象語法樹時(shí)將此對(duì)象傳遞給當(dāng)前訪問的元素。
UML結(jié)構(gòu)圖:
代碼示例:
下面是一個(gè)visitor模式的簡(jiǎn)單示例督笆。我們用CarElement來接收一個(gè)visitor對(duì)象芦昔,在visitor類中對(duì)car的不同組成部分實(shí)現(xiàn)不同的visit方法。
public interface CarElementVisitor
{
void visit(Wheel wheel);
void visit(Engine engine);
void visit(Body body);
void visit(Car car);
}
public interface CarElement
{
void accept(CarElementVisitor visitor);
}
public class Wheel : CarElement
{
private string name;
public Wheel(string name)
{
this.name = name;
}
public void accept(CarElementVisitor visitor)
{
visitor.visit(this);
}
}
public class Engine : CarElement
{
public void accept(CarElementVisitor visitor)
{
visitor.visit(this);
}
}
public class Body : CarElement
{
public void accept(CarElementVisitor visitor)
{
visitor.visit(this);
}
}
public class Car : CarElement
{
private List<CarElement> elements;
public Car()
{
this.elements = new List<CarElement>()
{
};
}
public void accept(CarElementVisitor visitor)
{
foreach(CarElement element in elements)
{
element.accept(visitor);
}
visitor.visit(this);
}
}
public class CarElementPrintVisitor : CarElementVisitor
{
public void visit(Wheel wheel)
{
Console.WriteLine("Visiting wheel")
}
public void visit(Engine engine)
{
Console.WriteLine("Visiting engine")
}
public void visit(Body body)
{
Console.WriteLine("Visiting body")
}
public void visit(Car car)
{
Console.WriteLine("Visiting car")
}
}
適用情況:
(1) 一個(gè)對(duì)象結(jié)構(gòu)包含很多類對(duì)象娃肿,它們有不同的接口咕缎,而你想對(duì)這些對(duì)象實(shí)施一些依賴其具體類的操作珠十;
(2) 需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作,而你想避免讓這些操作“污染”這些對(duì)象的類凭豪;
(3) 定義對(duì)象結(jié)構(gòu)的類很少改變焙蹭。但經(jīng)常需要在此結(jié)構(gòu)上定義新的操作,改變對(duì)象結(jié)構(gòu)類需要重定義對(duì)所有訪問者的接口嫂伞,這可能需要很大的代價(jià)孔厉。
參考文獻(xiàn):
《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》
Chain-of-responsibility pattern
Command pattern
Iterator pattern
Mediator pattern
Memento pattern
Template method pattern
Visitor pattern
C# 中的委托和事件