1. 程式人生 > >看透設計模式-行為型模式

看透設計模式-行為型模式

 本文主要討論行為型模式

職責鏈模式:

 

職責鏈模式(Chain of Responsibility  Pattern):避免請求傳送者與接收者耦合在一起,讓多個物件都有可能接收請求,將這些物件連線成一條鏈,並且沿著這條鏈傳遞請求,直到有物件處理它為止。職責鏈模式是一種物件行為型模式。

      職責鏈模式結構的核心在於引入了一個抽象處理者、職責鏈模式的核心在於抽象處理者類的設計
---------------------

● Handler(抽象處理者):它定義了一個處理請求的介面,一般設計為抽象類

,由於不同的具體處理者處理請求的方式不同,因此在其中定義了抽象請求處理方法。因為每一個處理者的下家還是一個處理者,因此在抽象處理者中定義了一個抽象處理者型別的物件(如結構圖中的successor),作為其對下家的引用。通過該引用,處理者可以連成一條鏈。

● ConcreteHandler(具體處理者):它是抽象處理者的子類,可以處理使用者請求,在具體處理者類中實現了抽象處理者中定義的抽象請求處理方法,在處理請求之前需要進行判斷,看是否有相應的處理許可權,如果可以處理請求就處理它,否則將請求轉發給後繼者;在具體處理者中可以訪問鏈中下一個物件,以便請求的轉發。

      在職責鏈模式裡,很多物件由每一個物件對其下家的引用而連線起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個物件決定處理此請求。發出這個請求的客戶端並不知道鏈上的哪一個物件最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織鏈和分配責任。
---------------------  

ConcreteHandlerA ConcreteHandlerB的繼承可以看做一個,加上successor的聚合引用關係,就兩個 關係,所以一般來說, 職責鏈模式是很容易理解和使用的。

具體處理者是抽象處理者的子類,它具有兩大作用:第一是處理請求,不同的具體處理者以不同的形式實現抽象請求處理方法handleRequest();第二是轉發請求,如果該請求超出了當前處理者類的許可權,可以將該請求轉發給下家。—— 需要檢查 許可權是否足夠。

需要注意的是,職責鏈模式並不建立職責鏈,職責鏈的建立工作必須由系統的其他部分來完成,一般是在使用該職責鏈的客戶端中建立職責鏈。職責鏈模式降低了請求的傳送端和接收端之間的耦合,使多個物件都有機會處理這個請求。

 

  (1) 純的職責鏈模式

      一個純的職責鏈模式要求一個具體處理者物件只能在兩個行為中選擇一個:要麼承擔全部責任,要麼將責任推給下家,不允許出現某一個具體處理者物件在承擔了一部分或全部責任後又將責任向下傳遞的情況。而且在純的職責鏈模式中,要求一個請求必須被某一個處理者物件所接收,不能出現某個請求未被任何一個處理者物件處理的情況。在前面的採購單審批例項中應用的是純的職責鏈模式。

 

       (2)不純的職責鏈模式

      在一個不純的職責鏈模式中允許某個請求被一個具體處理者部分處理後再向下傳遞,或者一個具體處理者處理完某請求後其後繼處理者可以繼續處理該請求,而且一個請求可以最終不被任何處理者物件所接收。Java AWT 1.0中的事件處理模型應用的是不純的職責鏈模式,其基本原理如下:由於視窗元件(如按鈕、文字框等)一般都位於容器元件中,因此當事件發生在某一個元件上時,先通過元件物件的handleEvent()方法將事件傳遞給相應的事件處理方法,該事件處理方法將處理此事件,然後決定是否將該事件向上一級容器元件傳播;上級容器元件在接到事件之後可以繼續處理此事件並決定是否繼續向上級容器元件傳播,如此反覆,直到事件到達頂層容器元件為止;如果一直傳到最頂層容器仍沒有處理方法,則該事件不予處理。每一級元件在接收到事件時,都可以處理此事件,而不論此事件是否在上一級已得到處理,還存在事件未被處理的情況。顯然,這就是不純的職責鏈模式,早期的Java AWT事件模型(JDK 1.0及更早)中的這種事件處理機制又叫事件浮升(Event Bubbling)機制。從Java.1.1以後,JDK使用觀察者模式代替職責鏈模式來處理事件。目前,在JavaScript中仍然可以使用這種事件浮升機制來進行事件處理。
---------------------  

  職責鏈模式的主要優點如下:

       (1) 職責鏈模式使得一個物件無須知道是其他哪一個物件處理其請求,物件僅需知道該請求會被處理即可,接收者和傳送者都沒有對方的明確資訊,且鏈中的物件不需要知道鏈的結構,由客戶端負責鏈的建立,降低了系統的耦合度。

       (2) 請求處理物件僅需維持一個指向其後繼者的引用,而不需要維持它對所有的候選處理者的引用,可簡化物件的相互連線。

       (3) 在給物件分派職責時,職責鏈可以給我們更多的靈活性,可以通過在執行時對該鏈進行動態的增加或修改來增加或改變處理一個請求的職責。

       (4) 在系統中增加一個新的具體請求處理者時無須修改原有系統的程式碼,只需要在客戶端重新建鏈即可,從這一點來看是符合“開閉原則”的。

      

       2.主要缺點

      職責鏈模式的主要缺點如下:

       (1) 由於一個請求沒有明確的接收者,那麼就不能保證它一定會被處理,該請求可能一直到鏈的末端都得不到處理;一個請求也可能因職責鏈沒有被正確配置而得不到處理。

       (2) 對於比較長的職責鏈,請求的處理可能涉及到多個處理物件,系統性能將受到一定影響,而且在進行程式碼除錯時不太方便。

       (3) 如果建鏈不當,可能會造成迴圈呼叫,將導致系統陷入死迴圈。
---------------------  

3.適用場景

      在以下情況下可以考慮使用職責鏈模式:

       (1) 有多個物件可以處理同一個請求,具體哪個物件處理該請求待執行時刻再確定,客戶端只需將請求提交到鏈上,而無須關心請求的處理物件是誰以及它是如何處理的。

       (2) 在不明確指定接收者的情況下,向多個物件中的一個提交一個請求。

       (3) 可動態指定一組物件處理請求,客戶端可以動態建立職責鏈來處理請求,還可以改變鏈中處理者之間的先後次序。 
---------------------  

示例: 攔截器、過濾器、請假、報銷審批(需要按額度 層層判斷的那種)

拓展性:可以靈活的增加一個Handler。 由客戶端可以動態建立職責鏈來處理請求。

簡化: 只有一個繼承關係+ 聚合關係,顯然,都是不可或缺,因而無法再繼續簡化了

討論

主要解決了 多重判斷 的問題。就像跟女人一樣,我們都喜歡純的,但是實際遇到不純的,也不少,實屬正常,不可強求一定要“純的”。

Handler 是否可以有多個handlerRequest方法,多個successor? 顯然是不可以的, 否則 問題就複雜了。

 

命令模式:

 

命令模式(Command Pattern):將一個請求封裝為一個物件,從而讓我們可用不同的請求對客戶進行引數化;對請求排隊或者記錄請求日誌,以及支援可撤銷的操作。命令模式是一種物件行為型模式,其別名為動作(Action)模式或事務(Transaction)模式。

       命令模式的定義比較複雜,提到了很多術語,例如“用不同的請求對客戶進行引數化”、“對請求排隊”,“記錄請求日誌”、“支援可撤銷操作”等,在後面我們將對這些術語進行一一講解。

       命令模式的核心在於引入了命令類,通過命令類來降低傳送者和接收者的耦合度,請求傳送者只需指定一個命令物件,再通過命令物件來呼叫請求接收者的處理方法
---------------------  

● Command(抽象命令類):抽象命令類一般是一個抽象類或介面,在其中聲明瞭用於執行請求的execute()等方法,通過這些方法可以呼叫請求接收者的相關操作。

● ConcreteCommand(具體命令類):具體命令類是抽象命令類的子類,實現了在抽象命令類中宣告的方法,它對應具體的接收者物件,將接收者物件的動作繫結其中。在實現execute()方法時,將呼叫接收者物件的相關操作(Action)。

● Invoker(呼叫者):呼叫者即請求傳送者,它通過命令物件來執行請求。一個呼叫者並不需要在設計時確定其接收者,因此它只與抽象命令類之間存在關聯關係。在程式執行時可以將一個具體命令物件注入其中,再呼叫具體命令物件的execute()方法,從而實現間接呼叫請求接收者的相關操作。

● Receiver(接收者):接收者執行與請求相關的操作,它具體實現對請求的業務處理。
---------------------  

命令模式可以將請求傳送者和接收者完全解耦,傳送者與接收者之間沒有直接引用關係,傳送請求的物件只需要知道如何傳送請求,而不必知道如何完成請求

命令模式的本質是對請求進行封裝,一個請求對應於一個命令,將發出命令的責任和執行命令的責任分割開。每一個命令都是一個操作:請求的一方發出請求要求執行一個操作;接收的一方收到請求,並執行相應的操作。命令模式允許請求的一方和接收的一方獨立開來,使得請求的一方不必知道接收請求的一方的介面,更不必知道請求如何被接收、操作是否被執行、何時被執行,以及是怎麼被執行的。

       命令模式的關鍵在於引入了抽象命令類,請求傳送者針對抽象命令類程式設計,只有實現了抽象命令類的具體命令才與請求接收者相關聯。在最簡單的抽象命令類中只包含了一個抽象的execute()方法,每個具體命令類將一個Receiver型別的物件作為一個例項變數進行儲存,從而具體指定一個請求的接收者,不同的具體命令類提供了execute()方法的不同實現,並呼叫不同接收者的請求處理方法。
---------------------  

命令模式是一種使用頻率非常高的設計模式,它可以將請求傳送者與接收者解耦,請求傳送者通過命令物件來間接引用請求接收者,使得系統具有更好的靈活性和可擴充套件性。在基於GUI的軟體開發,無論是在電腦桌面應用還是在移動應用中,命令模式都得到了廣泛的應用。

 

       1. 主要優點

       命令模式的主要優點如下:

       (1) 降低系統的耦合度。由於請求者與接收者之間不存在直接引用,因此請求者與接收者之間實現完全解耦,相同的請求者可以對應不同的接收者,同樣,相同的接收者也可以供不同的請求者使用,兩者之間具有良好的獨立性。

       (2) 新的命令可以很容易地加入到系統中。由於增加新的具體命令類不會影響到其他類,因此增加新的具體命令類很容易,無須修改原有系統原始碼,甚至客戶類程式碼,滿足“開閉原則”的要求。

       (3) 可以比較容易地設計一個命令佇列或巨集命令(組合命令)。

       (4) 為請求的撤銷(Undo)和恢復(Redo)操作提供了一種設計和實現方案。

 

       2. 主要缺點

       命令模式的主要缺點如下:

       使用命令模式可能會導致某些系統有過多的具體命令類。因為針對每一個對請求接收者的呼叫操作都需要設計一個具體命令類,因此在某些系統中可能需要提供大量的具體命令類,這將影響命令模式的使用。

 

       3. 適用場景

      在以下情況下可以考慮使用命令模式:

       (1) 系統需要將請求呼叫者和請求接收者解耦,使得呼叫者和接收者不直接互動。請求呼叫者無須知道接收者的存在,也無須知道接收者是誰,接收者也無須關心何時被呼叫。

       (2) 系統需要在不同的時間指定請求、將請求排隊和執行請求。一個命令物件和請求的初始呼叫者可以有不同的生命期,換言之,最初的請求發出者可能已經不在了,而命令物件本身仍然是活動的,可以通過該命令物件去呼叫請求接收者,而無須關心請求呼叫者的存在性,可以通過請求日誌檔案等機制來具體實現。

       (3) 系統需要支援命令的撤銷(Undo)操作和恢復(Redo)操作。

       (4) 系統需要將一組操作組合在一起形成巨集命令。
---------------------  

上面的UML圖其實畫得不太正確, 它並不能幫助我們理解命令模式的精髓。

我們看到,總共有: 一個繼承關係+ 聚合關係+關聯關係。需要特別注意的是, Receiver 應該是一個具體的 ConcreteReceiver , 它和 ConcreteCommand 一 一對應,這樣,我們才會明白, 原來是這樣 解耦的啊!! 所謂的解耦, 無非就是“多了一箇中間層” ,也就是多了command 層。Invoker 實際需要的是Receiver 的工作,但是現在多了 command ,現在Invoker 根本不需要的是Receiver 了,command 可以隨意的切換Receiver , 從而 解耦了! 

 

討論:

一個請求傳送者能否對應多個請求接收者?如何實現? ___ 一個請求傳送者可以對應多個請求接受者,將單個Command的引用改為List,然後在執行時遍歷呼叫。

—— 命令佇列,“批處理”

Receiver 是否定義成一個介面,·從而讓ConcreteCommand 依賴介面,從而可以靈活的替換 ConcreteReceiver 呢?  可以這樣,但是不是必須的。

Command 隨意的切換Receiver , 是否會破壞 開閉原則?是的, ConcreteCommand 定下來的那一刻,那麼其對應的ConcreteReceiver 的應該已經確定了的,所以不能隨意切換,儘管如此, 但是 Invoker 這一層是不需要變動的! 如果新增 Command ,Invoker也是不需要變動的!

Invoker 和 Command 的關係是 1v1 還是1 v N ? 完全可以是1vN ( 命令佇列 ),client 去使用ConcreteCommand 即可。

Invoker 只要一個例項嗎?no,不同 Invoker 擁有不同Command 即可。

Invoker 可以有多個 方法嗎?可以的,比如一個計算器,有加減乘除等操作,每個操作要求能夠取消,即有 compute,undo 方法,那麼各個具體的計算操作分別實現兩者即可。

簡化: 一個繼承關係+ 聚合關係+關聯關係,無法再簡化了

示例:Struts2 的ActionServlet,Invoker 只有一個,實際的Action為command ,是非常多的,ConcreteCommand類 呼叫Receiver(對應?) 完成具體工作, 按需增加。

拓展性:新的命令可以很容易地加入到系統中。

控制面板、等等

 

直譯器模式:

 

直譯器模式(Interpreter Pattern):定義一個語言的文法,並且建立一個直譯器來解釋該語言中的句子,這裡的“語言”是指使用規定格式和語法的程式碼。直譯器模式是一種類行為型模式。

       由於表示式可分為終結符表示式和非終結符表示式,因此直譯器模式的結構與組合模式的結構有些類似,但在直譯器模式中包含更多的組成元素
---------------------  

 

● AbstractExpression(抽象表示式):在抽象表示式中聲明瞭抽象的解釋操作,它是所有終結符表示式和非終結符表示式的公共父類。

       ● TerminalExpression(終結符表示式):終結符表示式是抽象表示式的子類,它實現了與文法中的終結符相關聯的解釋操作,在句子中的每一個終結符都是該類的一個例項。通常在一個直譯器模式中只有少數幾個終結符表示式類,它們的例項可以通過非終結符表示式組成較為複雜的句子。

       ● NonterminalExpression(非終結符表示式):非終結符表示式也是抽象表示式的子類,它實現了文法中非終結符的解釋操作,由於在非終結符表示式中可以包含終結符表示式,也可以繼續包含非終結符表示式,因此其解釋操作一般通過遞迴的方式來完成。

       ● Context(環境類):環境類又稱為上下文類,它用於儲存直譯器之外的一些全域性資訊,通常它臨時儲存了需要解釋的語句。

       當系統無須提供全域性公共資訊時可以省略環境類,可根據實際情況決定是否需要環境類。

---------------------  

TerminalExpression 不可再細分,NonterminalExpression 是TerminalExpression 和 其他NonterminalExpression  的組合。

總結:

直譯器模式為自定義語言的設計和實現提供了一種解決方案,它用於定義一組文法規則並通過這組文法規則來解釋語言中的句子。雖然直譯器模式的使用頻率不是特別高,但是它在正則表示式、XML文件解釋等領域還是得到了廣泛使用。與直譯器模式類似,目前還誕生了很多基於抽象語法樹的原始碼處理工具,例如Eclipse中的Eclipse AST,它可以用於表示Java語言的語法結構,使用者可以通過擴充套件其功能,建立自己的文法規則。

      1. 主要優點

      直譯器模式的主要優點如下:

      (1) 易於改變和擴充套件文法。由於在直譯器模式中使用類來表示語言的文法規則,因此可以通過繼承等機制來改變或擴充套件文法。

      (2) 每一條文法規則都可以表示為一個類,因此可以方便地實現一個簡單的語言。

      (3) 實現文法較為容易。在抽象語法樹中每一個表示式節點類的實現方式都是相似的,這些類的程式碼編寫都不會特別複雜,還可以通過一些工具自動生成節點類程式碼。

      (4) 增加新的解釋表示式較為方便。如果使用者需要增加新的解釋表示式只需要對應增加一個新的終結符表示式或非終結符表示式類,原有表示式類程式碼無須修改,符合“開閉原則”。

      2. 主要缺點

      直譯器模式的主要缺點如下:

      (1) 對於複雜文法難以維護。在直譯器模式中,每一條規則至少需要定義一個類,因此如果一個語言包含太多文法規則,類的個數將會急劇增加,導致系統難以管理和維護,此時可以考慮使用語法分析程式等方式來取代直譯器模式。

      (2) 執行效率較低。由於在直譯器模式中使用了大量的迴圈和遞迴呼叫,因此在解釋較為複雜的句子時其速度很慢,而且程式碼的除錯過程也比較麻煩。

      3. 適用場景

      在以下情況下可以考慮使用直譯器模式:

      (1) 可以將一個需要解釋執行的語言中的句子表示為一個抽象語法樹。

      (2) 一些重複出現的問題可以用一種簡單的語言來進行表達。

      (3) 一個語言的文法較為簡單。

      (4) 執行效率不是關鍵問題。【注:高效的直譯器通常不是通過直接解釋抽象語法樹來實現的,而是需要將它們轉換成其他形式,使用直譯器模式的執行效率並不高。】
---------------------  

迭代器模式:

 

在軟體開發中,我們經常需要使用聚合物件來儲存一系列資料。聚合物件擁有兩個職責:一是儲存資料;二是遍歷資料。從依賴性來看,前者是聚合物件的基本職責;而後者既是可變化的,又是可分離的。因此,可以將遍歷資料的行為從聚合物件中分離出來,封裝在一個被稱之為“迭代器”的物件中,由迭代器來提供遍歷聚合物件內部資料的行為,這將簡化聚合物件的設計,更符合“單一職責原則”的要求。

       迭代器模式定義如下:

迭代器模式(Iterator Pattern):提供一種方法來訪問聚合物件,而不用暴露這個物件的內部表示,其別名為遊標(Cursor)。迭代器模式是一種物件行為型模式。

       在迭代器模式結構中包含聚合和迭代器兩個層次結構,考慮到系統的靈活性和可擴充套件性,在迭代器模式中應用了工廠方法模式
---------------------  

理解迭代器模式中具體聚合類與具體迭代器類之間存在的依賴關係和關聯關係。 —— 相對於相關關聯。

主要優點

       迭代器模式的主要優點如下:

       (1) 它支援以不同的方式遍歷一個聚合物件,在同一個聚合物件上可以定義多種遍歷方式。在迭代器模式中只需要用一個不同的迭代器來替換原有迭代器即可改變遍歷演算法,我們也可以自己定義迭代器的子類以支援新的遍歷方式。

       (2) 迭代器簡化了聚合類。由於引入了迭代器,在原有的聚合物件中不需要再自行提供資料遍歷等方法,這樣可以簡化聚合類的設計。

       (3) 在迭代器模式中,由於引入了抽象層,增加新的聚合類和迭代器類都很方便,無須修改原有程式碼,滿足“開閉原則”的要求。

 

       2. 主要缺點

       迭代器模式的主要缺點如下:

       (1) 由於迭代器模式將儲存資料和遍歷資料的職責分離,增加新的聚合類需要對應增加新的迭代器類,類的個數成對增加,這在一定程度上增加了系統的複雜性。

       (2) 抽象迭代器的設計難度較大,需要充分考慮到系統將來的擴充套件,例如JDK內建迭代器Iterator就無法實現逆向遍歷,如果需要實現逆向遍歷,只能通過其子類ListIterator等來實現,而ListIterator迭代器無法用於操作Set型別的聚合物件。在自定義迭代器時,建立一個考慮全面的抽象迭代器並不是件很容易的事情。

 

       3. 適用場景

       在以下情況下可以考慮使用迭代器模式:

       (1) 訪問一個聚合物件的內容而無須暴露它的內部表示。將聚合物件的訪問與內部資料的儲存分離,使得訪問聚合物件時無須瞭解其內部實現細節。

       (2) 需要為一個聚合物件提供多種遍歷方式。

       (3) 為遍歷不同的聚合結構提供一個統一的介面,在該介面的實現類中為不同的聚合結構提供不同的遍歷方式,而客戶端可以一致性地操作該介面。
---------------------  

 

中介者模式:

從:

變成了:

如果在一個系統中物件之間存在多對多的相互關係,我們可以將物件之間的一些互動行為從各個物件中分離出來,並集中封裝在一箇中介者物件中,並由該中介者進行統一協調,這樣物件之間多對多的複雜關係就轉化為相對簡單的一對多關係。通過引入中介者來簡化物件之間的複雜互動,中介者模式是“迪米特法則”的一個典型應用。
--------------------- 

中介者模式定義如下:

中介者模式(Mediator Pattern):用一箇中介物件(中介者)來封裝一系列的物件互動,中介者使各物件不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。中介者模式又稱為調停者模式,它是一種物件行為型模式。

      在中介者模式中,我們引入了用於協調其他物件/類之間相互呼叫的中介者類,為了讓系統具有更好的靈活性和可擴充套件性,通常還提供了抽象中介者
---------------------  

 中介者模式 感覺跟 外觀模式 很像,其實不然,中介者模式 針對的是 系統內部, 相互的呼叫,雙向的。 外觀模式 關注的是 外部系統對內部各個系統的呼叫,是單向的。

● Mediator(抽象中介者):它定義一個介面,該介面用於與各同事物件之間進行通訊。

      ● ConcreteMediator(具體中介者):它是抽象中介者的子類,通過協調各個同事物件來實現協作行為,它維持了對各個同事物件的引用。

      ● Colleague(抽象同事類):它定義各個同事類公有的方法,並聲明瞭一些抽象方法來供子類實現,同時它維持了一個對抽象中介者類的引用,其子類可以通過該引用來與中介者通訊。

      ● ConcreteColleague(具體同事類):它是抽象同事類的子類;每一個同事物件在需要和其他同事物件通訊時,先與中介者通訊,通過中介者來間接完成與其他同事類的通訊;在具體同事類中實現了在抽象同事類中宣告的抽象方法。

 

      中介者模式的核心在於中介者類的引入,在中介者模式中,中介者類承擔了兩方面的職責:

       (1) 中轉作用(結構性):通過中介者提供的中轉作用,各個同事物件就不再需要顯式引用其他同事,當需要和其他同事進行通訊時,可通過中介者來實現間接呼叫。該中轉作用屬於中介者在結構上的支援。

      (2) 協調作用(行為性):中介者可以更進一步的對同事之間的關係進行封裝,同事可以一致的和中介者進行互動,而不需要指明中介者需要具體怎麼做,中介者根據封裝在自身內部的協調邏輯,對同事的請求進行進一步處理,將同事成員之間的關係行為進行分離和封裝。該協調作用屬於中介者在行為上的支援。
---------------------  

在具體同事類ConcreteColleague中實現了在抽象同事類中宣告的方法,其中方法method1()是同事類的自身方法(Self-Method),用於處理自己的行為,而方法method2()是依賴方法(Depend-Method),用於呼叫在中介者中定義的方法,依賴中介者來完成相應的行為,例如呼叫另一個同事類的相關方法
---------------------  

如何理解同事類中的自身方法與依賴方法? 一個有外部依賴Mediator ,一個沒有。

 

中介者模式將一個網狀的系統結構變成一個以中介者物件為中心的星形結構,在這個星型結構中,使用中介者物件與其他物件的一對多關係來取代原有物件之間的多對多關係。中介者模式在事件驅動類軟體中應用較為廣泛,特別是基於GUI(Graphical User Interface,圖形使用者介面)的應用軟體,此外,在類與類之間存在錯綜複雜的關聯關係的系統中,中介者模式都能得到較好的應用。

 

       1. 主要優點

       中介者模式的主要優點如下:

       (1) 中介者模式簡化了物件之間的互動,它用中介者和同事的一對多互動代替了原來同事之間的多對多互動,一對多關係更容易理解、維護和擴充套件,將原本難以理解的網狀結構轉換成相對簡單的星型結構。

      (2) 中介者模式可將各同事物件解耦。中介者有利於各同事之間的鬆耦合,我們可以獨立的改變和複用每一個同事和中介者,增加新的中介者和新的同事類都比較方便,更好地符合“開閉原則”。

      (3) 可以減少子類生成,中介者將原本分佈於多個物件間的行為集中在一起,改變這些行為只需生成新的中介者子類即可,這使各個同事類可被重用,無須對同事類進行擴充套件。

 

      2. 主要缺點

      中介者模式的主要缺點如下:

      在具體中介者類中包含了大量同事之間的互動細節,可能會導致具體中介者類非常複雜,使得系統難以維護。

 

      3. 適用場景

      在以下情況下可以考慮使用中介者模式:

      (1) 系統中物件之間存在複雜的引用關係,系統結構混亂且難以理解。

      (2) 一個物件由於引用了其他很多物件並且直接和這些物件通訊,導致難以複用該物件。

      (3) 想通過一箇中間類來封裝多個類中的行為,而又不想生成太多的子類。可以通過引入中介者類來實現,在中介者中定義物件互動的公共行為,如果需要改變行為則可以增加新的具體中介者類。
---------------------  

拓展性: 如果需要引入新的具體同事類,只需要繼承抽象同事類並實現其中的方法即可,由於具體同事類之間並無直接的引用關係,因此原有所有同事類無須進行任何修改,它們與新增同事物件之間的互動可以通過修改或者增加具體中介者類來實現;如果需要在原有系統中增加新的具體中介者類,只需要繼承抽象中介者類(或已有的具體中介者類)並覆蓋其中定義的方法即可,在新的具體中介者中可以通過不同的方式來處理物件之間的互動,也可以增加對新增同事的引用和呼叫。在客戶端中只需要修改少許程式碼(如果引入配置檔案的話有時可以不修改任何程式碼)就可以實現中介者的更換。 

 

討論:

ConcreteMediator是必須的嗎? 感覺如果簡單的話,可以不用吧~!

Mediator 通常有哪些介面方法? 協調各個 Colleague 的各種方法, 會不會非常多?

 ConcreteColleague 如果沒有統一實現Colleague 怎麼辦? 這種情況 能夠通過中介來 協調嗎?

 

個人感覺這個設計模式, 不算是真正的設計模式,不如說成一個 設計思想 更好吧~!

 

備忘錄模式:

備忘錄模式提供了一種狀態恢復的實現機制,使得使用者可以方便地回到一個特定的歷史步驟,當新的狀態無效或者存在問題時,可以使用暫時儲存起來的備忘錄將狀態復原,當前很多軟體都提供了撤銷(Undo)操作,其中就使用了備忘錄模式。

      備忘錄模式定義如下:

備忘錄模式(Memento Pattern):在不破壞封裝的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態,這樣可以在以後將物件恢復到原先儲存的狀態。它是一種物件行為型模式,其別名為Token。

      備忘錄模式的核心是備忘錄類以及用於管理備忘錄的負責人類的設計
---------------------

● Originator(原發器):它是一個普通類,可以建立一個備忘錄,並存儲它的當前內部狀態,也可以使用備忘錄來恢復其內部狀態,一般將需要儲存內部狀態的類設計為原發器。

      ●Memento(備忘錄):儲存原發器的內部狀態,根據原發器來決定儲存哪些內部狀態。備忘錄的設計一般可以參考原發器的設計,根據實際需要確定備忘錄類中的屬性。需要注意的是,除了原發器本身與負責人類之外,備忘錄物件不能直接供其他類使用,原發器的設計在不同的程式語言中實現機制會有所不同。

      ●Caretaker(負責人):負責人又稱為管理者,它負責儲存備忘錄,但是不能對備忘錄的內容進行操作或檢查。在負責人類中可以儲存一個或多個備忘錄物件,它只負責儲存物件,而不能修改物件,也無須知道物件的實現細節。

      理解備忘錄模式並不難,但關鍵在於如何設計備忘錄類和負責人類。由於在備忘錄中儲存的是原發器的中間狀態,因此需要防止原發器以外的其他物件訪問備忘錄,特別是不允許其他物件來修改備忘錄。
---------------------  

在設計備忘錄類時需要考慮其封裝性,除了Originator類,不允許其他類來呼叫備忘錄類Memento的建構函式與相關方法,如果不考慮封裝性,允許其他類呼叫setState()等方法,將導致在備忘錄中儲存的歷史狀態發生改變,通過撤銷操作所恢復的狀態就不再是真實的歷史狀態,備忘錄模式也就失去了本身的意義。
--------------------- 

對於負責人類Caretaker,它用於儲存備忘錄物件,並提供getMemento()方法用於向客戶端返回一個備忘錄物件,原發器通過使用這個備忘錄物件可以回到某個歷史狀態

 

再談備忘錄的封裝
      備忘錄是一個很特殊的物件,只有原發器對它擁有控制的權力,負責人只負責管理,而其他類無法訪問到備忘錄,因此我們需要對備忘錄進行封裝。

      為了實現對備忘錄物件的封裝,需要對備忘錄的呼叫進行控制,對於原發器而言,它可以呼叫備忘錄的所有資訊,允許原發器訪問返回到先前狀態所需的所有資料;對於負責人而言,只負責備忘錄的儲存並將備忘錄傳遞給其他物件;對於其他物件而言,只需要從負責人處取出備忘錄物件並將原發器物件的狀態恢復,而無須關心備忘錄的儲存細節。理想的情況是隻允許生成該備忘錄的那個原發器訪問備忘錄的內部狀態。

      在實際開發中,原發器與備忘錄之間的關係是非常特殊的,它們要分享資訊而不讓其他類知道,實現的方法因程式語言的不同而有所差異,在C++中可以使用friend關鍵字,讓原發器類和備忘錄類成為友元類,互相之間可以訪問物件的一些私有的屬性;在Java語言中可以將原發器類和備忘錄類放在一個包中,讓它們之間滿足預設的包內可見性,也可以將備忘錄類作為原發器類的內部類,使得只有原發器才可以訪問備忘錄中的資料,其他物件都無法使用備忘錄中的資料。
---------------------  

備忘錄模式在很多軟體的使用過程中普遍存在,但是在應用軟體開發中,它的使用頻率並不太高,因為現在很多基於窗體和瀏覽器的應用軟體並沒有提供撤銷操作。如果需要為軟體提供撤銷功能,備忘錄模式無疑是一種很好的解決方案。在一些字處理軟體、影象編輯軟體、資料庫管理系統等軟體中備忘錄模式都得到了很好的應用。

 

      1.主要優點

      備忘錄模式的主要優點如下:

      (1)它提供了一種狀態恢復的實現機制,使得使用者可以方便地回到一個特定的歷史步驟,當新的狀態無效或者存在問題時,可以使用暫時儲存起來的備忘錄將狀態復原。

      (2)備忘錄實現了對資訊的封裝,一個備忘錄物件是一種原發器物件狀態的表示,不會被其他程式碼所改動。備忘錄儲存了原發器的狀態,採用列表、堆疊等集合來儲存備忘錄物件可以實現多次撤銷操作。

 

      2.主要缺點

      備忘錄模式的主要缺點如下:

      資源消耗過大,如果需要儲存的原發器類的成員變數太多,就不可避免需要佔用大量的儲存空間,每儲存一次物件的狀態都需要消耗一定的系統資源。

 

      3.適用場景

      在以下情況下可以考慮使用備忘錄模式:

      (1)儲存一個物件在某一個時刻的全部狀態或部分狀態,這樣以後需要時它能夠恢復到先前的狀態,實現撤銷操作。

      (2)防止外界物件破壞一個物件歷史狀態的封裝性,避免將物件歷史狀態的實現細節暴露給外界物件。
---------------------  

 

 

討論:

能否通過原型模式來建立備忘錄物件?系統該如何設計? —— 也是可以吧。

 

觀察者模式:

 

 

觀察者模式是使用頻率最高的設計模式之一,它用於建立一種物件與物件之間的依賴關係,一個物件發生改變時將自動通知其他物件,其他物件將相應作出反應。在觀察者模式中,發生改變的物件稱為觀察目標,而被通知的物件稱為觀察者,一個觀察目標可以對應多個觀察者,而且這些觀察者之間可以沒有任何相互聯絡,可以根據需要增加和刪除觀察者,使得系統更易於擴充套件。

      觀察者模式定義如下:

觀察者模式(Observer Pattern):定義物件之間的一種一對多依賴關係,使得每當一個物件狀態發生改變時,其相關依賴物件皆得到通知並被自動更新。觀察者模式的別名包括髮布-訂閱(Publish/Subscribe)模式、模型-檢視(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式是一種物件行為型模式。

      觀察者模式結構中通常包括觀察目標和觀察者兩個繼承層次結構,
---------------------  

● Subject(目標):目標又稱為主題,它是指被觀察的物件。在目標中定義了一個觀察者集合,一個觀察目標可以接受任意數量的觀察者來觀察,它提供一系列方法來增加和刪除觀察者物件,同時它定義了通知方法notify()。目標類可以是介面,也可以是抽象類或具體類。

      ● ConcreteSubject(具體目標):具體目標是目標類的子類,通常它包含有經常發生改變的資料,當它的狀態發生改變時,向它的各個觀察者發出通知;同時它還實現了在目標類中定義的抽象業務邏輯方法(如果有的話)。如果無須擴充套件目標類,則具體目標類可以省略。

      ● Observer(觀察者):觀察者將對觀察目標的改變做出反應,觀察者一般定義為介面,該介面聲明瞭更新資料的方法update(),因此又稱為抽象觀察者。

      ● ConcreteObserver(具體觀察者):在具體觀察者中維護一個指向具體目標物件的引用,它儲存具體觀察者的有關狀態,這些狀態需要和具體目標的狀態保持一致;它實現了在抽象觀察者Observer中定義的update()方法。通常在實現時,可以呼叫具體目標類的attach()方法將自己新增到目標類的集合中或通過detach()方法將自己從目標類的集合中刪除。

      觀察者模式描述瞭如何建立物件與物件之間的依賴關係,以及如何構造滿足這種需求的系統。觀察者模式包含觀察目標和觀察者兩類物件,一個目標可以有任意數目的與之相依賴的觀察者,一旦觀察目標的狀態發生改變,所有的觀察者都將得到通知。作為對這個通知的響應,每個觀察者都將監視觀察目標的狀態以使其狀態與目標狀態同步,這種互動也稱為釋出-訂閱(Publish-Subscribe)。觀察目標是通知的釋出者,它發出通知時並不需要知道誰是它的觀察者,可以有任意數目的觀察者訂閱它並接收通知。
---------------------  

觀察者模式總結
      觀察者模式是一種使用頻率非常高的設計模式,無論是移動應用、Web應用或者桌面應用,觀察者模式幾乎無處不在,它為實現物件之間的聯動提供了一套完整的解決方案,凡是涉及到一對一或者一對多的物件互動場景都可以使用觀察者模式。觀察者模式廣泛應用於各種程式語言的GUI事件處理的實現,在基於事件的XML解析技術(如SAX2)以及Web事件處理中也都使用了觀察者模式。

      1.主要優點

      觀察者模式的主要優點如下:

      (1) 觀察者模式可以實現表示層和資料邏輯層的分離,定義了穩定的訊息更新傳遞機制,並抽象了更新介面,使得可以有各種各樣不同的表示層充當具體觀察者角色

      (2) 觀察者模式在觀察目標和觀察者之間建立一個抽象的耦合。觀察目標只需要維持一個抽象觀察者的集合,無須瞭解其具體觀察者。由於觀察目標和觀察者沒有緊密地耦合在一起,因此它們可以屬於不同的抽象化層次。

      (3) 觀察者模式支援廣播通訊,觀察目標會向所有已註冊的觀察者物件傳送通知,簡化了一對多系統設計的難度。

      (4) 觀察者模式滿足“開閉原則”的要求,增加新的具體觀察者無須修改原有系統程式碼,在具體觀察者與觀察目標之間不存在關聯關係的情況下,增加新的觀察目標也很方便。

      2.主要缺點

      觀察者模式的主要缺點如下:

      (1) 如果一個觀察目標物件有很多直接和間接觀察者,將所有的觀察者都通知到會花費很多時間。

      (2) 如果在觀察者和觀察目標之間存在迴圈依賴,觀察目標會觸發它們之間進行迴圈呼叫,可能導致系統崩潰。

      (3) 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標物件是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。

      3.適用場景

      在以下情況下可以考慮使用觀察者模式:

      (1) 一個抽象模型有兩個方面,其中一個方面依賴於另一個方面,將這兩個方面封裝在獨立的物件中使它們可以各自獨立地改變和複用。

      (2) 一個物件的改變將導致一個或多個其他物件也發生改變,而並不知道具體有多少物件將發生改變,也不知道這些物件是誰。

      (3) 需要在系統中建立一個觸發鏈,A物件的行為將影響B物件,B物件的行為將影響C物件……,可以使用觀察者模式建立一種鏈式觸發機制。
---------------------  

 

狀態模式:

 

狀態模式用於解決系統中複雜物件的狀態轉換以及不同狀態下行為的封裝問題。當系統中某個物件存在多個狀態,這些狀態之間可以進行轉換,而且物件在不同狀態下行為不相同時可以使用狀態模式。狀態模式將一個物件的狀態從該物件中分離出來,封裝到專門的狀態類中,使得物件狀態可以靈活變化,對於客戶端而言,無須關心物件狀態的轉換以及物件所處的當前狀態,無論對於何種狀態的物件,客戶端都可以一致處理。

       狀態模式定義如下:

狀態模式(State Pattern):允許一個物件在其內部狀態改變時改變它的行為,物件看起來似乎修改了它的類。其別名為狀態物件(Objects for States),狀態模式是一種物件行為型模式。

       在狀態模式中引入了抽象狀態類和具體狀態類,它們是狀態模式的核心
---------------------  

● Context(環境類):環境類又稱為上下文類,它是擁有多種狀態的物件。由於環境類的狀態存在多樣性且在不同狀態下物件的行為有所不同,因此將狀態獨立出去形成單獨的狀態類。在環境類中維護一個抽象狀態類State的例項,這個例項定義當前狀態,在具體實現時,它是一個State子類的物件。

       ● State(抽象狀態類):它用於定義一個介面以封裝與環境類的一個特定狀態相關的行為,在抽象狀態類中聲明瞭各種不同狀態對應的方法,而在其子類中實現類這些方法,由於不同狀態下物件的行為可能不同,因此在不同子類中方法的實現可能存在不同,相同的方法可以寫在抽象狀態類中。

       ● ConcreteState(具體狀態類):它是抽象狀態類的子類,每一個子類實現一個與環境類的一個狀態相關的行為,每一個具體狀態類對應環境的一個具體狀態,不同的具體狀態類其行為有所不同。

       在狀態模式中,我們將物件在不同狀態下的行為封裝到不同的狀態類中,為了讓系統具有更好的靈活性和可擴充套件性,同時對各狀態下的共有行為進行封裝,我們需要對狀態進行抽象,引入了抽象狀態類角色
---------------------  

在狀態模式的使用過程中,一個物件的狀態之間還可以進行相互轉換,通常有兩種實現狀態轉換的方式:

       (1) 統一由環境類來負責狀態之間的轉換  

       (2) 由具體狀態類來負責狀態之間的轉換

 

狀態模式 的狀態是 根據外部提供 資料, 動態進行變化的, 某條件下是這個狀態,其他條件是 另外的狀態。 總體而言, 狀態有 Context 驅動。。

 

狀態模式總結
       狀態模式將一個物件在不同狀態下的不同行為封裝在一個個狀態類中,通過設定不同的狀態物件可以讓環境物件擁有不同的行為,而狀態轉換的細節對於客戶端而言是透明的,方便了客戶端的使用。在實際開發中,狀態模式具有較高的使用頻率,在工作流和遊戲開發中狀態模式都得到了廣泛的應用,例如公文狀態的轉換、遊戲中角色的升級等。

 

       1. 主要優點

       狀態模式的主要優點如下:

       (1) 封裝了狀態的轉換規則,在狀態模式中可以將狀態的轉換程式碼封裝在環境類或者具體狀態類中,可以對狀態轉換程式碼進行集中管理,而不是分散在一個個業務方法中。

       (2) 將所有與某個狀態有關的行為放到一個類中,只需要注入一個不同的狀態物件即可使環境物件擁有不同的行為。

       (3) 允許狀態轉換邏輯與狀態物件合成一體,而不是提供一個巨大的條件語句塊,狀態模式可以讓我們避免使用龐大的條件語句來將業務方法和狀態轉換程式碼交織在一起。

       (4) 可以讓多個環境物件共享一個狀態物件,從而減少系統中物件的個數。

 

       2. 主要缺點

       狀態模式的主要缺點如下:

       (1) 狀態模式的使用必然會增加系統中類和物件的個數,導致系統執行開銷增大。

       (2) 狀態模式的結構與實現都較為複雜,如果使用不當將導致程式結構和程式碼的混亂,增加系統設計的難度。

       (3) 狀態模式對“開閉原則”的支援並不太好,增加新的狀態類需要修改那些負責狀態轉換的原始碼,否則無法轉換到新增狀態;而且修改某個狀態類的行為也需修改對應類的原始碼。

 

      3. 適用場景

      在以下情況下可以考慮使用狀態模式:

      (1) 物件的行為依賴於它的狀態(如某些屬性值),狀態的改變將導致行為的變化。

      (2) 在程式碼中包含大量與物件狀態有關的條件語句,這些條件語句的出現,會導致程式碼的可維護性和靈活性變差,不能方便地增加和刪除狀態,並且導致客戶類與類庫之間的耦合增強。
--------------------- 

適用於那些 狀態很多,很複雜,行為隨狀態發生“大變化” 的情況。

“允許狀態轉換邏輯與狀態物件合成一體,而不是提供一個巨大的條件語句塊” ———— 然而狀態模式 並不能完全的消除 if else 條件語句塊, 不過是把“巨大的條件語句塊” 換成了 “比較小的條件語句塊”

策略模式:

 

在策略模式中,我們可以定義一些獨立的類來封裝不同的演算法,每一個類封裝一種具體的演算法,在這裡,每一個封裝演算法的類我們都可以稱之為一種策略(Strategy),為了保證這些策略在使用時具有一致性,一般會提供一個抽象的策略類來做規則的定義,而每種演算法則對應於一個具體策略類。

      策略模式的主要目的是將演算法的定義與使用分開,也就是將演算法的行為和環境分開,將演算法的定義放在專門的策略類中,每一個策略類封裝了一種實現演算法,使用演算法的環境類針對抽象策略類進行程式設計,符合“依賴倒轉原則”。在出現新的演算法時,只需要增加一個新的實現了抽象策略類的具體策略類即可。策略模式定義如下:

策略模式(Strategy Pattern):定義一系列演算法類,將每一個演算法封裝起來,並讓它們可以相互替換,策略模式讓演算法獨立於使用它的客戶而變化,也稱為政策模式(Policy)。策略模式是一種物件行為型模式。

      策略模式結構並不複雜,但我們需要理解其中環境類Context的作用
---------------------  

 

● Context(環境類):環境類是使用演算法的角色,它在解決某個問題(即實現某個方法)時可以採用多種策略。在環境類中維持一個對抽象策略類的引用例項,用於定義所採用的策略。

      ● Strategy(抽象策略類):它為所支援的演算法聲明瞭抽象方法,是所有策略類的父類,它可以是抽象類或具體類,也可以是介面。環境類通過抽象策略類中宣告的方法在執行時呼叫具體策略類中實現的演算法。

      ● ConcreteStrategy(具體策略類):它實現了在抽象策略類中宣告的演算法,在執行時,具體策略類將覆蓋在環境類中定義的抽象策略類物件,使用一種具體的演算法實現某個業務處理。
--------------------- 

一個環境類Context能否對應多個不同的策略等級結構?如何設計? 當然可以的, 多一個不同等級結構 型別的Strategy 引用即可!

這個策略模式和簡單工廠模式具有異曲同工之妙呀~ 他們都是將if...else這樣難擴充套件和維護的程式碼通過繼承的方式來解決,然後針對抽象類程式設計,使之擴充套件的時候滿足“開閉原則

策略模式總結
      策略模式用於演算法的自由切換和擴充套件,它是應用較為廣泛的設計模式之一。策略模式對應於解決某一問題的一個演算法族,允許使用者從該演算法族中任選一個演算法來解決某一問題,同時可以方便地更換演算法或者增加新的演算法。只要涉及到演算法的封裝、複用和切換都可以考慮使用策略模式。

      1. 主要優點

      策略模式的主要優點如下:

      (1) 策略模式提供了對“開閉原則”的完美支援,使用者可以在不修改原有系統的基礎上選擇演算法或行為,也可以靈活地增加新的演算法或行為。

      (2) 策略模式提供了管理相關的演算法族的辦法。策略類的等級結構定義了一個演算法或行為族,恰當使用繼承可以把公共的程式碼移到抽象策略類中,從而避免重複的程式碼。

      (3) 策略模式提供了一種可以替換繼承關係的辦法。如果不使用策略模式,那麼使用演算法的環境類就可能會有一些子類,每一個子類提供一種不同的演算法。但是,這樣一來演算法的使用就和演算法本身混在一起,不符合“單一職責原則”,決定使用哪一種演算法的邏輯和該演算法本身混合在一起,從而不可能再獨立演化;而且使用繼承無法實現演算法或行為在程式執行時的動態切換。

      (4) 使用策略模式可以避免多重條件選擇語句。多重條件選擇語句不易維護,它把採取哪一種演算法或行為的邏輯與演算法或行為本身的實現邏輯混合在一起,將它們全部硬編碼(Hard Coding)在一個龐大的多重條件選擇語句中,比直接繼承環境類的辦法還要原始和落後。

      (5) 策略模式提供了一種演算法的複用機制,由於將演算法單獨提取出來封裝在策略類中,因此不同的環境類可以方便地複用這些策略類。

      2. 主要缺點

      策略模式的主要缺點如下:

      (1) 客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。這就意味著客戶端必須理解這些演算法的區別,以便適時選擇恰當的演算法。換言之,策略模式只適用於客戶端知道所有的演算法或行為的情況。

      (2) 策略模式將造成系統產生很多具體策略類,任何細小的變化都將導致系統要增加一個新的具體策略類。

      (3) 無法同時在客戶端使用多個策略類,也就是說,在使用策略模式時,客戶端每次只能使用一個策略類,不支援使用一個策略類完成部分功能後再使用另一個策略類來完成剩餘功能的情況。

      3. 適用場景

      在以下情況下可以考慮使用策略模式:

      (1) 一個系統需要動態地在幾種演算法中選擇一種,那麼可以將這些演算法封裝到一個個的具體演算法類中,而這些具體演算法類都是一個抽象演算法類的子類。換言之,這些具體演算法類均有統一的介面,根據“里氏代換原則”和麵向物件的多型性,客戶端可以選擇使用任何一個具體演算法類,並只需要維持一個數據型別是抽象演算法類的物件。

      (2) 一個物件有很多的行為,如果不用恰當的模式,這些行為就只好使用多重條件選擇語句來實現。此時,使用策略模式,把這些行為轉移到相應的具體策略類裡面,就可以避免使用難以維護的多重條件選擇語句。

      (3) 不希望客戶端知道複雜的、與演算法相關的資料結構,在具體策略類中封裝演算法與相關的資料結構,可以提高演算法的保密性與安全性。
--------------------- 

 

模板方法模式:

 

模板方法模式定義如下:

模板方法模式:定義一個操作中演算法的框架,而將一些步驟延遲到子類中。模板方法模式使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。

       模板方法模式是一種基於繼承的程式碼複用技術,它是一種類行為型模式。

       模板方法模式是結構最簡單的行為型設計模式,在其結構中只存在父類與子類之間的繼承關係。通過使用模板方法模式,可以將一些複雜流程的實現步驟封裝在一系列基本方法中,在抽象父類中提供一個稱之為模板方法的方法來定義這些基本方法的執行次序,而通過其子類來覆蓋某些步驟,從而使得相同的演算法框架可以有不同的執行結果。模板方法模式提供了一個模板方法來定義演算法框架,而某些具體步驟的實現可以在其子