第一篇:設計模式之創建型模式
第二篇:設計模式之結構型模式
在這部分里,我們關注GoF設計模式中的行為型模式,它是用來在不同對象之間划分職責和算法的抽象,行為模式不僅涉及到類和對象,還涉及到類與對象之間如何進行關聯。
行為型模式包括:職責鏈(Chain of Responsibility)、命令(Command)、解釋器(Interperter)、迭代(Iterator)、中介者(Mediator)、備忘錄(Memento)、觀察者(Observer)、狀態(State)、策略(Strategy)、模板(Template)和訪問者(Visitor)。我們主要討論其中的一部分模式,后續會有其他補充。
1) 職責鏈(Chain of Responsibility),如果完成一項業務,需要很多步相關操作,但是如果將這些操作完全封裝到一個類或者方法里面,又違背了單一職責的原則。這時我們可以考慮使用職責鏈模式,對應的UML圖如下:
我們可以創建很多個Handler的實現類,並通過設置Successor來將這些Handler“串”在一起。那么如何觸發所有的Handler呢?這里和Decorator有點兒類似,我們可以通過調用Successor.HandlerRequest來實現。這樣用戶只需要關心最開始的Handler,而不必關心后面都還有哪些其他的Handler。
2)命令(Command),命令模式將發出命令和執行命令很好的區分開來,當我們執行某項業務時,客戶端只需要構造一個請求,而不必關心業務實現的具體細節,即構造請求和業務實現是獨立的。對應的UML圖如下:
從圖中,我們可以看到,當Client端需要執行某項業務時,它需要構造一個Invoker對象,它負責發出請求,會生成一個Command對象。同時我們看到有一個Receiver對象,它是用來實現具體業務的,我們在ConcreteCommand中,會引用這個對象,來完成具體業務。
3)觀察者(Observer),當我們的系統中,存在一個業務A,有其他多個業務都需要關注業務A,當它的狀態發生變化時,其他業務都需要做出相應操作,這時我們可以使用觀察者模式。觀察者模式也稱作訂閱模式,它會定義一個“主題”(業務A),一個抽象的“訂閱者”以及很多具體的“訂閱者”(其他業務),在“主題”中,會保留所有“訂閱者”的引用,同時可以對“訂閱者”進行添加或者刪除,當“主題”的狀態發生變化時,它會主動“通知”所有“訂閱者”,從而“訂閱者”可以做出相應的操作。對應的UML圖如下:
我們可以看到ConcreteSubject中保留了多個Subscriber的引用(Subscribers),在NotifySubscriber方法中,它會依次調用每個Subscriber的Update方法,從而更新“訂閱者”的狀態。
4)訪問者(Visitor),當我們有一個對象集合,集合中的元素類型是不一樣的,但類型是相對固定的,例如只有3種不同的類型,但是可能有30個元素。如果我們希望對集合中的所有元素進行某種操作,從接口的角度來看,由於類型不一致,我們很難通過一個統一的接口來遍歷集合元素並對其進行操作。這時我們可以考慮使用訪問者模式,它將獲取某個元素和對元素進行操作進行了分離。對應的UML圖如下:
這里我們假設集合中只包括了2中不同的類型,ObjectBuilder就是上面提到的集合,它包含多個不同的IElement元素,業務的核心實現是在VisitorA和VisitorB中,對於Element1的Accept方法來說,它只是調用visitor.VisitElement1方法。
5)模板(Template),繼承是面向對象的一大核心,而模板方法就是對繼承的完美體現。對於某項業務來說,我們可以根據通用的流程,設計其方法骨架,針對不清晰或者不明確的地方,以抽象方法的方式來處理,然后根據不同的子業務,創建不同的子類,在子類中,實現那些抽象方法。對應的UML圖如下:
可以看出,對於子類來說,它是不需要重寫Operate方法的,而只需要實現父類的抽象方法。對於客戶端來說,當它實例化某個子類后,可以直接調用Operate方法來完成某項業務。
6)策略(Strategy),當我們的系統中,針對某項業務有多個算法時,如何對這些算法進行管理,我們可以考慮使用策略模式,它主要是針對一組可以提取相同接口的算法進行管理。對應的UML圖如下:
這里需要注意的是,Strategy類並不知道應該使用哪個具體的子類,這應該由Client指定。
7)解釋器(Interperter),如果我們的系統中有些特定的問題反復出現,我們想要對這些問題進行抽象,那應該如何做?試想一下,當我們寫完代碼后,是如何進行編譯的?無論對C#還是Java,它們的編譯器都會讀取我們所寫的每一行代碼,並作出相應的解釋。我們可以部分認為,編譯器中存儲了任何組合的語句,類似於C中的typedef。解釋器做的就是類似的事情,它將具有通用性的問題進行抽取,對其解決方案進行綜合處理。對應的UML圖如下:
一般的執行過程是這樣的,Client讀取Context中的信息,根據某種原則將其划分成多個部分,針對每一部分,構造相應的解釋器,並將Context信息傳入解釋器中進行處理。這里的問題是Client必須要清楚Context細節和具體解釋器中間的關聯。我們可以在Client和Interpreter之間構造一個“解釋器工廠”,用來根據Context生成相應的解釋器實例,同樣,如果解釋器的執行過程和數據無關,我們可以為“解釋器工廠”上追加“單例”模式,構造一個解釋器池。這些都是可以根據需求做的進一步的優化。
8)迭代(Iterator),前文提到的訪問者(Visitor)模式,針對的是存儲在一起的不同類型的對象集合,如何進行遍歷處理,那么針對存儲在一起的相同類型的對象集合,我們應該如何進行遍歷呢?迭代模式可以幫我們做到,對應的UML圖如下:
在C#和Java中,我們都已經在語言層級上實現了迭代,例如C#中的foreach,同時.NET來設計了兩個接口來實現迭代:IEnumerator和IEnumerable。
9)中介者(Mediator),如果我們的系統中有多個對象,彼此之間都有聯系,那這是一個對象之間耦合很高的系統,我們應該如何優化呢?我們可以建立一個知道所有對象的“對象”,在它內部維護其他對象之間的關聯,這就是中介者模式,對應的UML圖如下:
這里,Mediator是知道所有IPerson的“底細”的,Client可以直接與Mediator聯系,而不必關心具體的是PersonA還是PersonB,同樣,對於PersonA和PersonB來說,它們之間也沒有直接聯系,當兩者需要通信時,之金額使用Mediator的Send方法。
這個模式不好的地方在於:1)所有的IPerson類型都要有Mediator引用,這樣才能和其他的Person通信;2)Mediator需要維護所有Person的實例,這樣它才能做出正確的判斷,將消息發給對應的Person,但當Person子類過多時,Mediator就變的較難維護,這時,我們可以創建一套關於產生Person實例的“工廠”,會減輕Mediator的負擔。
10)備忘錄(Memento),當我們的系統中存在這樣一種對象,它的屬性很多,在某些情況下,它的一部分屬性是需要進行備份和恢復的,那應該如何做?談到備份和恢復,我們立刻想到可以使用原型模式,但那是針對所有屬性的,備忘錄模式可以很好地解決這里的問題,對應的UML圖如下:
在這里,我們希望Originator的State2、State3是可以備份和恢復的,其他屬性是無關的。我們可以在希望備份Originator的地方,調用Creatememento方法,在希望恢復Originator部分屬性的地方,調用RestoreMemento方法,同時MementoManager對Memento進行管理。
11)狀態(State),當我們的系統中的對象,需要根據傳入的不同參數,進行不同的處理,而且傳入參數的種類特別多,這時在方法內部會產生大量的if語句,來確定方法的執行分支。那么如何消除這些if語句呢?狀態模式可以幫我們做到,對應的UML圖如下:
這里,Client只與Context關聯,在Context內部,會維護不同狀態之間的跳轉,簡單來說,就是在HandleRequest內部判斷傳入的state值,如果符合業務邏輯,那么直接調用state的HandleRequest方法;如果不符合,那么修改state值,然后調用相應state的HandleRequest方法。