行為型模式用於描述程序在運行時復雜的流程控制,即描述多個類或對象之間怎樣相互協作共同完成單個對象都無法單獨完成的任務,它涉及算法與對象間職責的分配。
行為型模式分為類行為模式和對象行為模式,前者采用繼承機制來在類間分派行為,后者采用組合或聚合在對象間分配行為。由於組合關系或聚合關系比繼承關系耦合度低,滿足“合成復用原則”,所以對象行為模式比類行為模式具有更大的靈活性。
行為型模式是 GoF 設計模式中最為龐大的一類,它包含以下 11 種模式。
- 模板方法(Template Method)模式:定義一個操作中的算法骨架,將算法的一些步驟延遲到子類中,使得子類在可以不改變該算法結構的情況下重定義該算法的某些特定步驟。
- 策略(Strategy)模式:定義了一系列算法,並將每個算法封裝起來,使它們可以相互替換,且算法的改變不會影響使用算法的客戶。
- 命令(Command)模式:將一個請求封裝為一個對象,使發出請求的責任和執行請求的責任分割開。
- 職責鏈(Chain of Responsibility)模式:把請求從鏈中的一個對象傳到下一個對象,直到請求被響應為止。通過這種方式去除對象之間的耦合。
- 狀態(State)模式:允許一個對象在其內部狀態發生改變時改變其行為能力。
- 觀察者(Observer)模式:多個對象間存在一對多關系,當一個對象發生改變時,把這種改變通知給其他多個對象,從而影響其他對象的行為。
- 中介者(Mediator)模式:定義一個中介對象來簡化原有對象之間的交互關系,降低系統中對象間的耦合度,使原有對象之間不必相互了解。
- 迭代器(Iterator)模式:提供一種方法來順序訪問聚合對象中的一系列數據,而不暴露聚合對象的內部表示。
- 訪問者(Visitor)模式:在不改變集合元素的前提下,為一個集合中的每個元素提供多種訪問方式,即每個元素有多個訪問者對象訪問。
- 備忘錄(Memento)模式:在不破壞封裝性的前提下,獲取並保存一個對象的內部狀態,以便以后恢復它。
- 解釋器(Interpreter)模式:提供如何定義語言的文法,以及對語言句子的解釋方法,即解釋器。
以上 11 種行為型模式,除了模板方法模式和解釋器模式是類行為型模式,其他的全部屬於對象行為型模式,下面我將詳細介紹它們的特點、結構與應用。
模板方法模式
在面向對象程序設計過程中,程序員常常會遇到這種情況:設計一個系統時知道了算法所需的關鍵步驟,而且確定了這些步驟的執行順序,但某些步驟的具體實現還未知,或者說某些步驟的實現與具體的環境相關。
例如,去銀行辦理業務一般要經過以下4個流程:取號、排隊、辦理具體業務、對銀行工作人員進行評分等,其中取號、排隊和對銀行工作人員進行評分的業務對每個客戶是一樣的,可以在父類中實現,但是辦理具體業務卻因人而異,它可能是存款、取款或者轉賬等,可以延遲到子類中實現。
模式的定義與特點
模板方法(Template Method)模式的定義如下:定義一個操作中的算法骨架,而將算法的一些步驟延遲到子類中,使得子類可以不改變該算法結構的情況下重定義該算法的某些特定步驟。它是一種類行為型模式。
該模式的主要優點如下。
- 它封裝了不變部分,擴展可變部分。它把認為是不變部分的算法封裝到父類中實現,而把可變部分算法由子類繼承實現,便於子類繼續擴展。
- 它在父類中提取了公共的部分代碼,便於代碼復用。
- 部分方法是由子類實現的,因此子類可以通過擴展方式增加相應的功能,符合開閉原則。
該模式的主要缺點如下。
- 對每個不同的實現都需要定義一個子類,這會導致類的個數增加,系統更加龐大,設計也更加抽象。
- 父類中的抽象方法由子類實現,子類執行的結果會影響父類的結果,這導致一種反向的控制結構,它提高了代碼閱讀的難度。
模式的結構與實現
模板方法模式需要注意抽象類與具體子類之間的協作。它用到了虛函數的多態性技術以及“不用調用我,讓我來調用你”的反向控制技術。現在來介紹它們的基本結構。
1. 模式的結構
模板方法模式包含以下主要角色。
(1) 抽象類(Abstract Class):負責給出一個算法的輪廓和骨架。它由一個模板方法和若干個基本方法構成。這些方法的定義如下。
1. 模板方法:定義了算法的骨架,按某種順序調用其包含的基本方法。
2.基本方法:是整個算法中的一個步驟,包含以下幾種類型。
- 抽象方法:在抽象類中申明,由具體子類實現。
- 具體方法:在抽象類中已經實現,在具體子類中可以繼承或重寫它。
- 鈎子方法:在抽象類中已經實現,包括用於判斷的邏輯方法和需要子類重寫的空方法兩種。
(2) 具體子類(Concrete Class):實現抽象類中所定義的抽象方法和鈎子方法,它們是一個頂級邏輯的一個組成步驟。
模板方法模式的結構圖如圖 1 所示。

圖1 模板方法模式的結構圖
模式的應用場景
模板方法模式通常適用於以下場景。
- 算法的整體步驟很固定,但其中個別部分易變時,這時候可以使用模板方法模式,將容易變的部分抽象出來,供子類實現。
- 當多個子類存在公共的行為時,可以將其提取出來並集中到一個公共父類中以避免代碼重復。首先,要識別現有代碼中的不同之處,並且將不同之處分離為新的操作。最后,用一個調用這些新的操作的模板方法來替換這些不同的代碼。
- 當需要控制子類的擴展時,模板方法只在特定點調用鈎子操作,這樣就只允許在這些點進行擴展。
模式的擴展
在模板方法模式中,基本方法包含:抽象方法、具體方法和鈎子方法,正確使用“鈎子方法”可以使得子類控制父類的行為。如下面例子中,可以通過在具體子類中重寫鈎子方法 HookMethod1() 和 HookMethod2() 來改變抽象父類中的運行結果,其結構圖如圖2 所示。

圖2 含鈎子方法的模板方法模式的結構圖
策略模式
在現實生活中常常遇到實現某種目標存在多種策略可供選擇的情況,例如,出行旅游可以乘坐飛機、乘坐火車、騎自行車或自己開私家車等,超市促銷可以釆用打折、送商品、送積分等方法。
在軟件開發中也常常遇到類似的情況,當實現某一個功能存在多種算法或者策略,我們可以根據環境或者條件的不同選擇不同的算法或者策略來完成該功能,如數據排序策略有冒泡排序、選擇排序、插入排序、二叉樹排序等。
如果使用多重條件轉移語句實現(即硬編碼),不但使條件語句變得很復雜,而且增加、刪除或更換算法要修改原代碼,不易維護,違背開閉原則。如果采用策略模式就能很好解決該問題。
策略模式的定義與特點
策略(Strategy)模式的定義:該模式定義了一系列算法,並將每個算法封裝起來,使它們可以相互替換,且算法的變化不會影響使用算法的客戶。策略模式屬於對象行為模式,它通過對算法進行封裝,把使用算法的責任和算法的實現分割開來,並委派給不同的對象對這些算法進行管理。
策略模式的主要優點如下。
- 多重條件語句不易維護,而使用策略模式可以避免使用多重條件語句。
- 策略模式提供了一系列的可供重用的算法族,恰當使用繼承可以把算法族的公共代碼轉移到父類里面,從而避免重復的代碼。
- 策略模式可以提供相同行為的不同實現,客戶可以根據不同時間或空間要求選擇不同的。
- 策略模式提供了對開閉原則的完美支持,可以在不修改原代碼的情況下,靈活增加新算法。
- 策略模式把算法的使用放到環境類中,而算法的實現移到具體策略類中,實現了二者的分離。
其主要缺點如下。
- 客戶端必須理解所有策略算法的區別,以便適時選擇恰當的算法類。
- 策略模式造成很多的策略類。
策略模式的結構與實現
策略模式是准備一組算法,並將這組算法封裝到一系列的策略類里面,作為一個抽象策略類的子類。策略模式的重心不是如何實現算法,而是如何組織這些算法,從而讓程序結構更加靈活,具有更好的維護性和擴展性,現在我們來分析其基本結構和實現方法。
1. 模式的結構
策略模式的主要角色如下。
- 抽象策略(Strategy)類:定義了一個公共接口,各種不同的算法以不同的方式實現這個接口,環境角色使用這個接口調用不同的算法,一般使用接口或抽象類實現。
- 具體策略(Concrete Strategy)類:實現了抽象策略定義的接口,提供具體的算法實現。
- 環境(Context)類:持有一個策略類的引用,最終給客戶端調用。
其結構圖如圖 1 所示。

圖1 策略模式的結構圖
策略模式的應用場景
策略模式在很多地方用到,如JavaSE 中的容器布局管理就是一個典型的實例,Java SE 中的每個容器都存在多種布局供用戶選擇。在程序設計中,通常在以下幾種情況中使用策略模式較多。
- 一個系統需要動態地在幾種算法中選擇一種時,可將每個算法封裝到策略類中。
- 一個類定義了多種行為,並且這些行為在這個類的操作中以多個條件語句的形式出現,可將每個條件分支移入它們各自的策略類中以代替這些條件語句。
- 系統中各算法彼此完全獨立,且要求對客戶隱藏具體算法的實現細節時。
- 系統要求使用算法的客戶不應該知道其操作的數據時,可使用策略模式來隱藏與算法相關的數據結構。
- 多個類只區別在表現行為不同,可以使用策略模式,在運行時動態選擇具體要執行的行為。
策略模式的擴展
在一個使用策略模式的系統中,當存在的策略很多時,客戶端管理所有策略算法將變得很復雜,如果在環境類中使用策略工廠模式來管理這些策略類將大大減少客戶端的工作復雜度,其結構圖如圖2所示。

圖2策略工廠模式的結構圖
命令模式
在軟件開發系統中,常常出現“方法的請求者”與“方法的實現者”之間存在緊密的耦合關系。這不利於軟件功能的擴展與維護。例如,想對行為進行“撤銷、重做、記錄”等處理都很不方便,因此“如何將方法的請求者與方法的實現者解耦?”變得很重要,命令模式能很好地解決這個問題。
在現實生活中,這樣的例子也很多,例如,電視機遙控器(命令發送者)通過按鈕(具體命令)來遙控電視機(命令接收者),還有計算機鍵盤上的“功能鍵”等。
命令模式的定義與特點
命令(Command)模式的定義如下:將一個請求封裝為一個對象,使發出請求的責任和執行請求的責任分割開。這樣兩者之間通過命令對象進行溝通,這樣方便將命令對象進行儲存、傳遞、調用、增加與管理。
命令模式的主要優點如下。
- 降低系統的耦合度。命令模式能將調用操作的對象與實現該操作的對象解耦。
- 增加或刪除命令非常方便。采用命令模式增加與刪除命令不會影響其他類,它滿足“開閉原則”,對擴展比較靈活。
- 可以實現宏命令。命令模式可以與組合模式結合,將多個命令裝配成一個組合命令,即宏命令。
- 方便實現 Undo 和 Redo 操作。命令模式可以與后面介紹的備忘錄模式結合,實現命令的撤銷與恢復。
其缺點是:可能產生大量具體命令類。因為計對每一個具體操作都需要設計一個具體命令類,這將增加系統的復雜性。
命令模式的結構與實現
可以將系統中的相關操作抽象成命令,使調用者與實現者相關分離,其結構如下。
1. 模式的結構
命令模式包含以下主要角色。
- 抽象命令類(Command)角色:聲明執行命令的接口,擁有執行命令的抽象方法 execute()。
- 具體命令角色(Concrete Command)角色:是抽象命令類的具體實現類,它擁有接收者對象,並通過調用接收者的功能來完成命令要執行的操作。
- 實現者/接收者(Receiver)角色:執行命令功能的相關操作,是具體命令對象業務的真正實現者。
- 調用者/請求者(Invoker)角色:是請求的發送者,它通常擁有很多的命令對象,並通過訪問命令對象來執行相關請求,它不直接訪問接收者。
其結構圖如圖 1 所示。

圖1 命令模式的結構圖
命令模式的應用場景
命令模式通常適用於以下場景。
- 當系統需要將請求調用者與請求接收者解耦時,命令模式使得調用者和接收者不直接交互。
- 當系統需要隨機請求命令或經常增加或刪除命令時,命令模式比較方便實現這些功能。
- 當系統需要執行一組操作時,命令模式可以定義宏命令來實現該功能。
- 當系統需要支持命令的撤銷(Undo)操作和恢復(Redo)操作時,可以將命令對象存儲起來,采用備忘錄模式來實現。
命令模式的擴展
在軟件開發中,有時將命令模式與前面學的組合模式聯合使用,這就構成了宏命令模式,也叫組合命令模式。宏命令包含了一組命令,它充當了具體命令與調用者的雙重角色,執行它時將遞歸調用它所包含的所有命令,其具體結構圖如圖2 所示。

圖2組合命令模式的結構圖
責任鏈模式
在現實生活中,常常會出現這樣的事例:一個請求有多個對象可以處理,但每個對象的處理條件或權限不同。例如,公司員工請假,可批假的領導有部門負責人、副總經理、總經理等,但每個領導能批准的天數不同,員工必須根據自己要請假的天數去找不同的領導簽名,也就是說員工必須記住每個領導的姓名、電話和地址等信息,這增加了難度。
模式的定義與特點
責任鏈(Chain of Responsibility)模式的定義:為了避免請求發送者與多個請求處理者耦合在一起,將所有請求的處理者通過前一對象記住其下一個對象的引用而連成一條鏈;當有請求發生時,可將請求沿着這條鏈傳遞,直到有對象處理它為止。
注意:責任鏈模式也叫職責鏈模式。
在責任鏈模式中,客戶只需要將請求發送到責任鏈上即可,無須關心請求的處理細節和請求的傳遞過程,所以責任鏈將請求的發送者和請求的處理者解耦了。
責任鏈模式是一種對象行為型模式,其主要優點如下。
- 降低了對象之間的耦合度。該模式使得一個對象無須知道到底是哪一個對象處理其請求以及鏈的結構,發送者和接收者也無須擁有對方的明確信息。
- 增強了系統的可擴展性。可以根據需要增加新的請求處理類,滿足開閉原則。
- 增強了給對象指派職責的靈活性。當工作流程發生變化,可以動態地改變鏈內的成員或者調動它們的次序,也可動態地新增或者刪除責任。
- 責任鏈簡化了對象之間的連接。每個對象只需保持一個指向其后繼者的引用,不需保持其他所有處理者的引用,這避免了使用眾多的 if 或者 if···else 語句。
- 責任分擔。每個類只需要處理自己該處理的工作,不該處理的傳遞給下一個對象完成,明確各類的責任范圍,符合類的單一職責原則。
其主要缺點如下。
- 不能保證每個請求一定被處理。由於一個請求沒有明確的接收者,所以不能保證它一定會被處理,該請求可能一直傳到鏈的末端都得不到處理。
- 對比較長的職責鏈,請求的處理可能涉及多個處理對象,系統性能將受到一定影響。
- 職責鏈建立的合理性要靠客戶端來保證,增加了客戶端的復雜性,可能會由於職責鏈的錯誤設置而導致系統出錯,如可能會造成循環調用。
模式的結構與實現
通常情況下,可以通過數據鏈表來實現職責鏈模式的數據結構。
1. 模式的結構
職責鏈模式主要包含以下角色。
- 抽象處理者(Handler)角色:定義一個處理請求的接口,包含抽象處理方法和一個后繼連接。
- 具體處理者(Concrete Handler)角色:實現抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的后繼者。
- 客戶類(Client)角色:創建處理鏈,並向鏈頭的具體處理者對象提交請求,它不關心處理細節和請求的傳遞過程。
其結構圖如圖 1 所示。客戶端可按圖 2 所示設置責任鏈。

圖1 責任鏈模式的結構圖

圖2 責任鏈
模式的應用場景
前邊已經講述了關於責任鏈模式的結構與特點,下面介紹其應用場景,責任鏈模式通常在以下幾種情況使用。
- 有多個對象可以處理一個請求,哪個對象處理該請求由運行時刻自動確定。
- 可動態指定一組對象處理請求,或添加新的處理者。
- 在不明確指定請求處理者的情況下,向多個處理者中的一個提交請求。
模式的擴展
職責鏈模式存在以下兩種情況。
- 純的職責鏈模式:一個請求必須被某一個處理者對象所接收,且一個具體處理者對某個請求的處理只能采用以下兩種行為之一:自己處理(承擔責任);把責任推給下家處理。
- 不純的職責鏈模式:允許出現某一個具體處理者對象在承擔了請求的一部分責任后又將剩余的責任傳給下家的情況,且一個請求可以最終不被任何接收端對象所接收。
狀態模式
在軟件開發過程中,應用程序中的有些對象可能會根據不同的情況做出不同的行為,我們把這種對象稱為有狀態的對象,而把影響對象行為的一個或多個動態變化的屬性稱為狀態。當有狀態的對象與外部事件產生互動時,其內部狀態會發生改變,從而使得其行為也隨之發生改變。如人的情緒有高興的時候和傷心的時候,不同的情緒有不同的行為,當然外界也會影響其情緒變化。
對這種有狀態的對象編程,傳統的解決方案是:將這些所有可能發生的情況全都考慮到,然后使用 if-else 語句來做狀態判斷,再進行不同情況的處理。但當對象的狀態很多時,程序會變得很復雜。而且增加新的狀態要添加新的 if-else 語句,這違背了“開閉原則”,不利於程序的擴展。
以上問題如果采用“狀態模式”就能很好地得到解決。狀態模式的解決思想是:當控制一個對象狀態轉換的條件表達式過於復雜時,把相關“判斷邏輯”提取出來,放到一系列的狀態類當中,這樣可以把原來復雜的邏輯判斷簡單化。
狀態模式的定義與特點
狀態(State)模式的定義:對有狀態的對象,把復雜的“判斷邏輯”提取到不同的狀態對象中,允許狀態對象在其內部狀態發生改變時改變其行為。
狀態模式是一種對象行為型模式,其主要優點如下。
- 狀態模式將與特定狀態相關的行為局部化到一個狀態中,並且將不同狀態的行為分割開來,滿足“單一職責原則”。
- 減少對象間的相互依賴。將不同的狀態引入獨立的對象中會使得狀態轉換變得更加明確,且減少對象間的相互依賴。
- 有利於程序的擴展。通過定義新的子類很容易地增加新的狀態和轉換。
狀態模式的主要缺點如下。
- 狀態模式的使用必然會增加系統的類與對象的個數。
- 狀態模式的結構與實現都較為復雜,如果使用不當會導致程序結構和代碼的混亂。
狀態模式的結構與實現
狀態模式把受環境改變的對象行為包裝在不同的狀態對象里,其意圖是讓一個對象在其內部狀態改變的時候,其行為也隨之改變。現在我們來分析其基本結構和實現方法。
1. 模式的結構
狀態模式包含以下主要角色。
- 環境(Context)角色:也稱為上下文,它定義了客戶感興趣的接口,維護一個當前狀態,並將與狀態相關的操作委托給當前狀態對象來處理。
- 抽象狀態(State)角色:定義一個接口,用以封裝環境對象中的特定狀態所對應的行為。
- 具體狀態(Concrete State)角色:實現抽象狀態所對應的行為。
其結構圖如圖 1 所示。

圖1 狀態模式的結構圖
狀態模式的應用場景
通常在以下情況下可以考慮使用狀態模式。
- 當一個對象的行為取決於它的狀態,並且它必須在運行時根據狀態改變它的行為時,就可以考慮使用狀態模式。
- 一個操作中含有龐大的分支結構,並且這些分支決定於對象的狀態時。
狀態模式的擴展
在有些情況下,可能有多個環境對象需要共享一組狀態,這時需要引入享元模式,將這些具體狀態對象放在集合中供程序共享,其結構圖如圖2所示。

圖2共享狀態模式的結構圖
分析:共享狀態模式的不同之處是在環境類中增加了一個 HashMap 來保存相關狀態,當需要某種狀態時可以從中獲取。