java设计模式概述
1.1?什么是設計模式
Christopher Alexander說過:“每個模式描寫敘述了一個在我們周圍不斷反復發生的問題,以及該問題的解決方式的核心。這樣,你就能一次重新地使用該方案而不必做反復勞動”
一般而言,一個模式有四個基本要素:
1.?模式名稱(pattern name) 一個助記名,它用一兩個詞來描寫敘述模式的問題、解決方式和效果。模式名能夠幫助我們思考,便于我們與其它人交流設計思想及設計結果。
找到恰當的模式名也是我們設計模式編目工作的難點之中的一個。
2.?問題(problem)?描寫敘述了應該在何時使用模式。它解釋了設計問題和問題存在的前因后果,它可能描寫敘述了特定的設計問題,如如何用對象表示算法等。
3.?解決方式(solution)?描寫敘述了設計的組成成分,它們之間的相互關系及各自的職責和協作方式。
由于模式就像一個模板,可應用于多種不同場合,所以解決方式并不描寫敘述一個特定而詳細的設計或實現,而是提供設計問題的抽象描寫敘述和如何用一個具有一般意義的元素組合
4.?效果(consequences)?描寫敘述了模式應用的效果及使用模式應權衡的問題。
雖然我們描寫敘述
1.2 Smalltalk MVC中的設計模式?
1.3?描寫敘述設計模式
我們如何描寫敘述設計模式呢?
圖形符號盡管非常重要也非常實用,卻還遠遠不夠,它們僅僅是將設計過程的結果簡單記錄為類和對象之間的關系。為了達到設計復用,我們必須同一時候記錄設計產生的決定過程、選擇過程和權衡過程。詳細的樣例也是非常重要的,它們讓你看到實際的設計。
我們將用統一的格式描寫敘述設計模式,每個模式依據下面的模板被分成若干部分。
模板具有統一的信息描寫敘述結構,有助于你更easy地學習、比較和使用設計模式。
模式名和分類
模式名簡潔地描寫敘述了模式的本質。一個好的名字很重要,由于它將成為你的設計詞匯表中的一部分。
模式的分類反映了我們將在?1 . 5?節 介 紹 的 方 案 。
意圖
是回答下列問題的簡單陳述:設計模式是做什么的?它的基本原理和意圖是什么?它解決的是什么樣的特定設計問題?
別名
模式的其它名稱。
動機
用以說明一個設計問題以及怎樣用模式中的類、對象來解決該問題的特定情景。該情景會幫助你理解隨后對模式更抽象的描寫敘述。
適用性
什么情況下能夠使用該設計模式?該模式可用來改進哪些不良設計?你如何識別這些情況?
結構
採用基于對象建模技術(?O M T?)?[ R B P + 9 1 ]?的 表 示 法 對 模 式 中 的 類 進 行 圖 形 描 述 。 我 們 也使用了交互圖?[ J C J O 9 2,?B O O 9 4 ]?來說明對象之間的請求序列和協作關系。
附錄?B?具體描寫敘述了這些表示法。
參與者
指設計模式中的類和?/或對象以及它們各自的職責。
協作
模式的參與者如何協作以實現它們的職責。
效果模式如何支持它的目標?
使用模式的效果和所需做的權衡取舍?
系統結構的哪些方面可
以獨立改變?
實現
實現模式時須要知道的一些提示、技術要點及應避免的缺陷,以及是否存在某些特定于實現語言的問題。
代碼演示樣例
用來說明如何用?C + +?或?S m a l l t a l k?實 現 該 模 式 的 代 碼 片 段 。
1.4?設計模式的編目
從第?3?章開始的模式文件夾中共包括?2 3?個 設 計 模 式 。 它 們 的 名 字 和 意 圖 列 舉 如 下 , 以 使 你 有個基本了解。每一個模式名后括號里標出模式所在的章節?(我們整本書都將遵從這個約定?)。
A b s t r a c t F a c t o r y?( 3 . 1 )?:提供一個創建一系列相關或相互依賴對象的接口,而無需指定它們詳細的類。
A d a p t er ( 4 . 1 )?:將一個類的接口轉換成客戶希望的另外一個接口。
?A d a p t e r?模式使得原本因為接口不兼容而不能一起工作的那些類能夠一起工作。
B r i d g e?( 4 . 2 )?:將抽象部分與它的實現部分分離,使它們都能夠獨立地變化。
B u i l d e r( 3 . 2 )?: 將 一 個 復 雜 對 象 的 構 建 與 它 的 表 示 分 離 , 使 得 同 樣 的 構 建 過 程 可 以 創 建 不同的表示。
C h a i n o f R e s p o n s i b i l i t y?( 5 . 1 ): 為 解 除 請 求 的 發 送 者 和 接 收 者 之 間 耦 合 , 而 使 多 個 對 象 都有機會處理這個請求。
將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理它。
C o m m a n d( 5 . 2 ):將一個請求封裝為一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日志,以及支持可取消的操作。
C o m p o s i t e?( 4 . 3 )?: 將 對 象 組 合 成 樹 形 結 構 以 表 示 “ 部 分?-整 體 ” 的 層 次 結 構 。?C o m p o s i t e?使得客戶對單個對象和復合對象的使用具有一致性。
D e c o r a t o r?( 4 . 4 )?:動態地給一個對象加入一些額外的職責。就擴展功能而言,?D e c o r a t o r?模式比生成子類方式更為靈活。
F a c a d e?( 4 . 5 ):為子系統中的一組接口提供一個一致的界面,?F a c a d e?模式定義了一個高層接口,這個接口使得這一子系統更加easy使用。
F a c t o r y M e t h o d?( 3 . 3 ):定義一個用于創建對象的接口,讓子類決定將哪一個類實例化。Factory Method使一個類的實例化延遲到其子類。
F l y w e i g h t?( 4 . 6 ):運用共享技術有效地支持大量細粒度的對象。
I n t e r p r e t e r?( 5 . 3 )?:給定一個語言?,?定義它的文法的一種表示,并定義一個解釋器?,?該解釋器使用該表示來解釋語言中的句子。
I t e r a t o r( 5 . 4 )?:提供一種方法順序訪問一個聚合對象中各個元素?,?而 又 不 需 暴 露 該 對 象 的內部表示。
M e d i a t o r?( 5 . 5 ): 用 一 個 中 介 對 象 來 封 裝 一 系 列 的 對 象 交 互 。 中 介 者 使 各 對 象 不 需 要 顯 式地相互引用,從而使其耦合松散,并且能夠獨立地改變它們之間的交互。
M e m e n t o( 5 . 6 )?: 在 不 破 壞 封 裝 性 的 前 提 下 , 捕 獲 一 個 對 象 的 內 部 狀 態 , 并 在 該 對 象 之 外保存這個狀態。
這樣以后就可將該對象恢復到保存的狀態。
O b s e r v e r?( 5 . 7 )?:定義對象間的一種一對多的依賴關系?,?以便當一個對象的狀態發生改變時?,全部依賴于它的對象都得到通知并自己主動刷新。
P r o t o t y p e?( 3 . 4 ): 用 原 型 實 例 指 定 創 建 對 象 的 種 類 , 并 且 通 過 拷 貝 這 個 原 型 來 創 建 新 的 對象。
P r o x y?( 4 . 7 ):為其它對象提供一個代理以控制對這個對象的訪問。
S i n g l e t o n( 3 . 5 )?:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
S t a t e( 5 . 8 )?: 允 許 一 個 對 象 在 其 內 部 狀 態 改 變 時 改 變 它 的 行 為 。 對 象 看 起 來 似 乎 修 改 了 它
所屬的類。
S t r a t e g y (5 . 9 )?: 定 義 一 系 列 的 算 法?,?把 它 們 一 個 個 封 裝 起 來?,?并 且 使 它 們 可 相 互 替 換 。 本 模
式使得算法的變化可獨立于使用它的客戶。
T e m p l a t e M e t h o d?( 5 . 1 0 )?:定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。
Template Method使得子類能夠不改變一個算法的結構就可以重定義該算法的某些特定步驟。
Vi s i t o r?( 5 . 11 ): 表 示 一 個 作 用 于 某 對 象 結 構 中 的 各 元 素 的 操 作 。
它 使 你 可 以 在 不 改 變 各 元
素的類的前提下定義作用于這些元素的新操作。
1.5?組織編目
設計模式在粒度和抽象層次上各不同樣。因為存在眾多的設計模式,我們希望用一種方式將它們組織起來。這一節將對設計模式進行分類以便于我們對各族相關的模式進行引用。分類有助于更快地學習文件夾中的模式,且對發現新的模式也有指導作用,如表1 - 1所看到的。
我 們 根 據 兩 條 準 則?(?表?1 - 1 )?對 模 式 進 行 分 類 。
第 一 是?目的?準 則 , 即 模 式 是 用 來 完 成 什 么 工作 的 。 模 式 依 據 其 目 的 可 分 為?創 建 型?(?C r e a t i o n a l?)、?結 構 型?( S t r u c t u r a l )?、 或?行 為 型( B e h a v i o r a l )?三種。
創建型模式與對象的創建有關;結構型模式處理類或對象的組合;行為型模式對類或對象如何交互和如何分配職責進行描寫敘述。
第二是?范圍?準則,指定模式主要是用于類還是用于對象。
類模式處理類和子類之間的關系,這些關系通過繼承建立,是靜態的,在編譯時刻便確定下來了。對象模式處理對象間的關系,這些關系在執行時刻是能夠變化的,更具動態性。
從某種意義上來說,差點兒全部模式都使用繼承機制,所以“類模式”僅僅指那些集中于處理類間關系的模式,而大部分模式都屬于對象模式的范疇。
創建型類模式將對象的部分創建工作延遲到子類,而創建型對象模式則將它延遲到還有一個對象中。
結構型類模式使用繼承機制來組合類,而結構型對象模式則描寫敘述了對象的組裝方式。行為型類模式使用繼承描寫敘述算法和控制流,而行為型對象模式則描寫敘述一組對象如何協作完畢單個對象所無法完畢的任務。
還 有 其 他 組 織 模 式 的 方 式 。
有 些 模 式 經 常 會 被 綁 在 一 起 使 用 , 例 如 ,?C o m p o s i t e?常和I t e r a t o r?或?V i s i t o r?一起使用;有些模式是可替代的,比如,?P r o t o t y p e?經常使用來替代?A b s t r a c tF a c t o r y?;有些模式雖然使用意圖不同,但產生的設計結果是非常相似的,比如,?C o m p o s i t e和D e c o r a t o r?的結構圖是相似的。
另一種方式是依據模式的“相關模式”部分所描寫敘述的它們如何互相引用來組織設計模式
客戶請求是使對象運行操作的唯一方法,操作又是對象改變內部數據的唯一方法。因為這些限制,對象的內部狀態是被封裝的,它不能被直接訪問,它的表示對于對象外部是不可見的。
面向對象設計最困難的部分是將系統分解成對象集合。
由于要考慮很多因素:封裝、粒度、依賴關系、靈活性、性能、演化、復用等等,它們都影響著系統的分解,而且這些因素通常還是互相沖突的。
面向對象設計方法學支持很多設計方法。你能夠寫出一個問題描寫敘述,挑出名詞和動詞,進而創建對應的類和操作;或者,你能夠關注于系統的協作和職責關系;或者,你能夠對現實世界建模,再將分析時發現的對象轉化至設計中。至于哪一種方法最好,并無定論。
設計的很多對象來源于現實世界的分析模型。
可是,設計結果所得到的類通常在現實世界中并不存在,有些是像數組之類的低層類,而還有一些則層次較高。比如,?C o m p o s i t e ( 4 . 3 )?模式引入了統一對待現實世界中并不存在的對象的抽象方法。嚴格反映當前現實世界的模型并不能產生也能反映將來世界的系統。設計中的抽象對于產生靈活的設計是至關重要的。
設計模式幫你確定并不明顯的抽象和描寫敘述這些抽象的對象。
比如,描寫敘述過程或算法的對象現實中并不存在,但它們卻是設計的關鍵部分。?S t r a t e g y ( 5 . 9 )?模 式 描 述 了 怎 樣 實 現 可 互 換 的算法族。?S t a t e ( 5 . 8 )?模 式 將 實 體 的 每 一 個 狀 態 描 述 為 一 個 對 象 。
這 些 對 象 在 分 析 階 段 , 甚 至 在設計階段的早期都并不存在,后來為使設計更靈活、復用性更好才將它們發掘出來。
1.6.2?決定對象的粒度
對象在大小和數目上變化極大。它們能表示下自硬件或上自整個應用的不論什么事物。那么我們如何決定一個對象應該是什么呢?
設計模式非常好地講述了這個問題。?F a c a d e ( 4 . 5 )?模式描寫敘述了如何用對象表示完整的子系統,F l y w e i g h t ( 4 . 6 )模 式 描 述 了 如 何 支 持 大 量 的 最 小 粒 度 的 對 象 。 其 他 一 些 設 計 模 式 描 述 了 將 一 個對象分解成很多小對象的特定方法。?A b s t r a c t F a c t o r y ( 3 . 1 )?和?B u i l d e r ( 3 . 2 )產生那些專門負責生成 其 他 對 象 的 對 象 。?Vi s i t o r ( 5 . 1 0 )?和?C o m m a n d ( 5 . 2 )?生 成 的 對 象 專 門 負 責 實 現 對 其 他 對 象 或 對 象組的請求。
1.6.3?指定對象接口
對象聲明的每個操作指定操作名、作為參數的對象和返回值,這就是所謂的操作的型構?( s i g n a t u r e )?。 對 象 操 作 所 定 義 的 所 有 操 作 型 構 的 集 合 被 稱 為 該 對 象 的?接口?( i n t e r f a c e )?。
對象接口描寫敘述了該對象所能接受的所有請求的集合,不論什么匹配對象接口中型構的請求都能夠發送給該對象。
類型?( t y p e )?是 用 來 標 識 特 定 接 口 的 一 個 名 字 。 如 果 一 個 對 象 接 受 “?W i n d o w?” 接 口 所 定 義的全部操作請求,那么我們就說該對象具有“?W i n d o w?” 類 型 。
一 個 對 象 可 以 有 許 多 類 型 , 而且不同的對象能夠共享同一個類型。對象接口的某部分能夠用某個類型來刻畫,而其它部分則可用其它類型刻畫。兩個類型同樣的對象僅僅須要共享它們的部分接口。接口能夠包括其它接口作為子集。當一個類型的接口包括還有一個類型的接口時,我們就說它是還有一個類型的子類型?( s u b t y p e )?, 另 一 個 類 型 稱 之 為 它 的?超類型?( s u p e r t y p e )?。
我 們 常 說 子 類 型?繼承?了 它 的 超 類 型的接口。
在面向對象系統中,接口是主要的組成部分。
對象僅僅有通過它們的接口才干與外部交流,假設不通過對象的接口就無法知道對象的不論什么事情,也無法請求對象做不論什么事情。對象接口與其功能實現是分離的,不同對象能夠對請求做不同的實現,也就是說,兩個有同樣接口的對象能夠有全然不同的實現。
當給對象發送請求時,所引起的詳細操作既與請求本身有關又與接受對象有關。支持同樣請求的不同對象可能對請求激發的操作有不同的實現。發送給對象的請求和它的對應操作在執行時刻的連接就稱之為動態綁定(dynamic binding)。
動態綁定是指發送的請求直到執行時刻才受你的詳細的實現的約束。因而,在知道不論什么有正確接口的對象都將接受此請求時,你能夠寫一個一般的程序,它期待著那些具有該特定接口的對象。進一步講,動態綁定同意你在執行時刻彼此替換有同樣接口的對象。
這樣的可替換 性 就 稱 為?多態?( p o l y m o r p h i s m )?, 它 是 面 向 對 象 系 統 中 的 核 心 概 念 之 一 。 多 態 允 許 客 戶 對 象 僅要求其它對象支持特定接口,除此之外對其如果幾近于無。多態簡化了客戶的定義,使得對象間彼此獨立,并能夠在執行時刻動態改變它們相互的關系。
設計模式通過確定接口的主要組成成分及經接口發送的數據類型,來幫助你定義接口。
設計模式或許還會告訴你接口中不應包含哪些東西。?M e m e n t o ( 5 . 6 )模 式 是 一 個 非常 好 的 例 子 ,它描寫敘述了如何封裝和保存對象內部的狀態,以便一段時間后對象能恢復到這一狀態。它規定了?M e m e n t o?對象必須定義兩個接口:一個同意客戶保持和復制?m e m e n t o?的 限 制 接 口 , 和 一 個 僅僅有原對象才干使用的用來儲存和提取?m e m e n t o?中 狀 態 的 特 權 接 口 。
設計模式也指定了接口之間的關系。
特別地,它們常常要求一些類具有相似的接口;或它們對一些類的接口做了限制。比如,?D e c o r a t o r ( 4 . 4 )和?P r o x y ( 4 . 7 )模式要求?D e c o r a t o r?和P r o x y對象的接口與被修飾的對象和受托付的對象一致。而?V i s i t o r ( 5 . 11 )模式中,?V i s i t o r接口必須反映出?v i s i t o r?能訪問的對象的全部類。
1.6.4?描寫敘述對象的實現
至此,我們非常少提及到實際上怎么去定義一個對象。對象的實現是由它的類決定的,類指定了對象的內部數據和表示,也定義了對象所能完畢的操作,如右圖所看到的。
我們基于?O M T?的 表 示 法 , 將 類 描 述 成 一 個 矩 形 , 其 中 的 類 名 以 黑體表示的。
操作在類名以下,以常規字體表示。類所定義的不論什么數據都在操作的以下。類名與操作之間以及操作與數據之間用橫線切割。
返回類型和實例變量類型是可選的,由于我們并未如果一定要用具有靜態類型的實現語言。
對象通過實例化?類來創建,此對象被稱為該類的實例。當實例化類時,要給對象的內部數據(由實例變量?組成)分配存儲空間,并將操作與這些數據聯系起來。
對象的很多類似實例是由實例化同一個類來創建的。
下圖中的虛箭頭線表示一個類實例化還有一個類的對象,箭頭指向被實例化的對象的類。新 的 類 可 以 由 已 存 在 的 類 通 過?類繼承?( c l a s s i n h e r i t a n c e )?來定義。當?子類?( s u b c l a s s )?繼承?父類(parent class)時,子類包括了父類定義的全部數據和操作。子類的實例對象包括全部子類和父類定義的數據,且它們能完畢子類和父類定義的全部操作。我們以豎線和三角表示子類關系,例如以下圖所看到的。
抽象類(abstract class)的主要目的是為它的子類定義公共接口。一個抽象類將把它的部分或所有操作的實現延遲到子類中,因此,一個抽象類不能被實例化。在抽象類中定義卻沒有實現的操作被稱為抽象操作(abstract operation)。
非抽象類稱為詳細類(concrete class)。
子 類 能 夠 改 進 和 重 新 定 義 它 們 父 類 的 操 作 。 更 具 體 地 說 , 類 能 夠?重定義?( o v e r r i d e )?父類定義的操作,重定義使得子類能接管父類對請求的處理操作。類繼承同意你僅僅需簡單的擴展其它類就能夠定義新類,從而能夠非常easy地定義具有相近功能的對象族。
抽象類的類名以斜體表示,以與詳細類相差別。抽象操作也用斜體表示。
圖中能夠包含實現操作的偽代碼,假設這樣,則代碼將出如今帶有摺角的框中,并用虛線將該摺角框與代碼所實現的操作相連,圖演示樣例如以下。
混入類(mixin class)是給其它類提供可選擇的接口或功能的類。它與抽象類一樣不能實例化。混入類要求多繼承,圖演示樣例如以下。
1.?類繼承與接口繼承的比較
理解對象的類( c l a?與s s對)?象的類型( t y p之e )間的區別很重要。一個對象的類定義了對象是如何實現的,同一時候也定義了對象的內部狀態和操作的實現。
類型,不同類的對象能夠有同樣的類型。當然,對象的類和類型是有緊密關系的。由于類定義了對象所能運行的操作,也定義了
對象的類型。
當我們說一個對象是一個類的實例時,即指該對象支持類所定義的接口。
C + +和?E i ff e l語言的類既指定對象的類型又指定對象的實現。
?S m a l l t a l k程序不聲明變量的類型,所以編譯器不檢查賦給變量的對象類型是否是該變量的類型的子類型。
發送消息時需
要檢查消息接收者是否實現了該消息,但不檢查接收者是否是某個特定類的實例。理解類繼承和接口繼承?(或子類型化?)之間的區別也十分重要。
類繼承依據一個對象的實現定義了還有一個對象的實現。
簡而言之,它是代碼和表示的共享機制。然而,接口繼承?(或子類
型化)描寫敘述了一個對象什么時候能被用來替代還有一個對象。由于很多語言并不顯式地區分這兩個概念,所以easy被混淆。在?C + +?和?E i ff e l語言中,繼
承 既 指 接 口 的 繼 承 又 指 實 現 的 繼 承 。
?C + +?中 接 口 繼 承 的 標 準 方 法 是 公 有 繼 承 一 個 含?(純?)?虛成員函數的類。?C + +?中 純 接 口 繼 承 接 近 于 公 有 繼 承 純 抽 象 類 , 純 實 現 繼 承 或 純 類 繼 承 接 近 于 私 有繼承。?S m a l l t a l k?中 的 繼 承 僅僅 指 實 現 繼 承 。 僅僅 要 任 何 類 的 實 例 支 持 對 變 量 值 的 操 作 , 你 就 可 以將這些實例賦給變量。
雖然大部分程序設計語言并不區分接口繼承和實現繼承的區別,但使用中人們還是分別對待它們的。?S m a l l t a l k?程序猿通常將子類當作子類型?(?雖然有一些熟知的例外情況?[ C o o 9 2 ] )?,C + +?程序猿通過抽象類所定義的類型來操縱對象。
非常多設計模式依賴于這樣的區別。
比如,?Chain of Responsibility(5.1)模式中的對象必須有一個公共的類型,但普通情況下它們不具有公共的實現。在?C o m p o s i t e ( 4 . 3 )?模 式 中 , 構 件 定 義 了一個公共的接口,但?C o m p o s i t e?通常定義一個公共的實現。?C o m m a n d ( 5 . 2 )?、?O b s e r v e r ( 5 . 7 )?、S t a t e ( 5 . 8 )?和?S t r a t e g y ( 5 . 9 )?通常純粹作為接口的抽象類來實現。
2.?對接口編程,而不是對實現編程
類繼承是一個通過復用父類功能而擴展應用功能的基本機制。它同意你依據舊對象高速定義新對象。它同意你從已存在的類中繼承所須要的絕大部分功能,從而差點兒無需不論什么代價就能夠獲得新的實現。
然而,實現的復用僅僅是成功的一半,繼承所擁有的定義具有同樣接口的對象族的能力也是非常重要的?(通常能夠從抽象類來繼承?)。為什么?
由于多態依賴于這樣的能力。
當繼承被恰當使用時,全部從抽象類導出的類將共享該抽象類的接口。這意味著子類只加入或重定義操作,而沒有隱藏父類的操作。這時,全部的子類都能響應抽象類接口中的請求,從而子類的類型都是抽象類的子類型。
僅僅依據抽象類中定義的接口來操縱對象有下面兩個優點:
1)?客戶無須知道他們使用對象的特定類型,僅僅須對象有客戶所期望的接口。
2)?客戶無須知道他們使用的對象是用什么類來實現的,他們僅僅須知道定義接口的抽象類。
這將極大地降低子系統實現之間的相互依賴關系,也產生了可復用的面向對象設計的如
下原則:
針對接口編程,而不是針對實現編程。
不將變量聲明為某個特定的詳細類的實例對象,而是讓它遵從抽象類所定義的接口。這是本書設計模式的一個常見主題。
當你不得不在系統的某個地方實例化詳細的類?(即指定一個特定的實現?)時,創建型模式
( A b s t r a c t F a c t o r y ( 3 . 1 )?,?B u i l d e r ( 3 . 2 ),?F a c t o r y M e t h o d ( 3 . 3 )?,?P r o t o t y p e ( 3 . 4 )?和?S i n g l e t o n ( 3 . 5 ) )?能夠幫你。通過抽象對象的創建過程,這些模式提供不同方式以在實例化時建立接口和實現的透明連接。創建型模式確保你的系統是採用針對接口的方式書寫的,而不是針對實現而書寫的。
1.6.5?運用復用機制
理解對象、接口、類和繼承之類的概念對大多數人來說并不難,問題的關鍵在于如何運用它們寫出靈活的、可復用的軟件。
設計模式將告訴你如何去做。
1.?繼承和組合的比較
面向對象系統中功能復用的兩種最經常使用技術是類繼承和對象組合(object composition)。
正如我們已解釋過的,類繼承同意你依據其它類的實現來定義一個類的實現。這樣的通過生成子類的復用通常被稱為白箱復用(white-box reuse)。術語“白箱”是相對可視性而言:在繼承方式中,父類的內部細節對子類可見。
對象組合是類繼承之外的還有一種復用選擇。新的更復雜的功能能夠通過組裝或組合對象來獲得。
對象組合要求被?組合?的對象具有良好定義的接口。這樣的復用風格被稱為?黑箱復用(black-box reuse),由于對象的內部細節是不可見的。對象僅僅以“黑箱”的形式出現。
繼承和組合各有優缺點。類繼承是在編譯時刻靜態定義的,且可直接使用,由于程序設計語言直接支持類繼承。
類繼承能夠較方便地改變被復用的實現。當一個子類重定義一些而不是所有操作時,它也能影響它所繼承的操作,僅僅要在這些操作中調用了被重定義的操作。
可是類繼承也有一些不足之處。
首先,由于繼承在編譯時刻就定義了,所以無法在執行時刻改變從父類繼承的實現。更糟的是,父類通常至少定義了部分子類的詳細表示。
由于繼承對子類揭示了其父類的實現細節,所以繼承常被覺得“破壞了封裝性”?[ S n y 8 6 ]?。
子類中的實現與它的父類有如此緊密的依賴關系,以至于父類實現中的不論什么變化必定會導致子類發生變化。
當你須要復用子類時,實現上的依賴性就會產生一些問題。
假設繼承下來的實現不適合解決新的問題,則父類必須重寫或被其它更適合的類替換。這樣的依賴關系限制了靈活性并終于限制了復用性。
一個可用的解決方法就是僅僅繼承抽象類,由于抽象類通常提供較少的實現。
對象組合是通過獲得對其它對象的引用而在執行時刻動態定義的。組合要求對象遵守彼此的接口約定,進而要求更細致地定義接口,而這些接口并最好還是礙你將一個對象和其它對象一起使用。這還會產生良好的結果:由于對象僅僅能通過接口訪問,所以我們并不破壞封裝性;僅僅要類型一致,執行時刻還能夠用一個對象來替代還有一個對象;更進一步,由于對象的實現是基于接口寫的,所以實現上存在較少的依賴關系。
對象組合對系統設計還有還有一個作用,即優先使用對象組合有助于你保持每一個類被封裝,并被集中在單個任務上。
這樣類和類繼承層次會保持較小規模,而且不太可能增長為不可控制的龐然大物。
還有一方面,基于對象組合的設計會有很多其它的對象?(而有較少的類),且系統的行為將依賴于對象間的關系而不是被定義在某個類中。
這導出了我們的面向對象設計的第二個原則:
優先使用對象組合,而不是類繼承。
理想情況下,你不應為獲得復用而去創建新的構件。你應該可以僅僅使用對象組合技術,
通過組裝已有的構件就能獲得你須要的功能。
可是事實非常少如此,由于可用構件的集合實際上并不足夠豐富。使用繼承的復用使得創建新的構件要比組裝舊的構件來得easy。這樣,繼承和對象組合常一起使用。
然而,我們的經驗表明:設計者往往過度使用了繼承這樣的復用技術。但依賴于對象組合技術的設計卻有更好的復用性?(或更簡單)。
你將會看到設計模式中一再使用對象組合技術。
2.?托付
托付(?d e l e g a t i o n?)?是一種組合方法,它使組合具有與繼承相同的復用能力?[ L i e 8 6 , J Z 9 1 ]?。在托付方式下,有兩個對象參與處理一個請求,接受請求的對象將操作托付給它的代理者
(d e l e g a t e)。這類似于子類將請求交給它的父類處理。
使用繼承時,被繼承的操作總能引用接受請求的對象,?C + +?中通過?t h i s?成員變量,?S m a l l t a l k?中則通過?s e l f?。
委 托 方 式 為 了 得 到 同 樣 的效果,接受請求的對象將自己傳給被托付者(代理人),使被托付的操作能夠引用接受請求的對象。
舉例來說,我們能夠在窗體類中保存一個矩形類的實例變量來代理矩形類的特定操作,這樣窗體類能夠復用矩形類的操作,而不必像繼承時那樣定義成矩形類的子類。
也就是說,一個窗體擁有一個矩形,而不是一個窗體就是一個矩形。窗體如今必須顯式的將請求轉發給它的矩形實例,而不是像曾經它必須繼承矩形的操作。
以下的圖顯示了窗體類將它的?A r e a?操 作 委 托 給 一 個 矩 形 實 例 。
箭 頭 線 表 示 一 個 類 對 另 一 個 類 實 例 的 引 用 關 系 。 引 用 名 是 可 選 的 , 本 例 為 “?r e c t a n g l e?”。
托付的主要長處在于它便于執行時刻組合對象操作以及改變這些操作的組合方式。假定矩形對象和圓對象有同樣的類型,我們僅僅需簡單的用圓對象替換矩形對象,則得到的窗體就是圓形的。
托付與那些通過對象組合以取得軟件靈活性的技術一樣,具有例如以下不足之處:動態的、高度參數化的軟件比靜態軟件更難于理解。還有執行低效問題,只是從長遠來看人的低效才是更基本的。僅僅有當托付使設計比較簡單而不是更復雜時,它才是好的選擇。要給出一個能確切告訴你什么時候能夠使用托付的規則是非常困難的。由于托付能夠得到的效率是與上下文有關的,而且還依賴于你的經驗。托付最適用于符合特定程式的情形,即標準模式的情形。
有一些模式使用了托付,如?S t a t e ( 5 . 8 )?、S t r a t e g y ( 5 . 9 )?和Vi s i t o r ( 5 . 11 )。在?S t a t e模式中,一個對象將請求托付給一個描寫敘述當前狀態的?S t a t e?對象來處理。在?S t r a t e g y?模 式 中 , 一 個 對 象 將 一個特定的請求托付給一個描寫敘述請求運行策略的對象,一個對象僅僅會有一個狀態,但它對不同的請求能夠有很多策略。這兩個模式的目的都是通過改變受托對象來改變托付對象的行為。在?V i s i t o r?中 , 對 象 結 構 的 每 個 元 素 上 的 操 作 總 是 被 委 托 到?Vi s i t o r?對象。
其 他 模 式 則 沒 有 這 么 多 地用到托付。
?M e d i a t o r ( 5 . 5 )引 進 了 一 個 中 介 其 他 對 象 間 通 信 的 對
象。有時,?M e d i a t o r?對 象 僅僅 是 簡 單 地 將 請 求 轉 發 給 其 他 對 象 ; 有 時 , 它 沿 著 指 向 自 己 的 引 用來傳遞請求,使用真正意義的托付。?Chain of Responsibility(5.1)通過將請求沿著對象鏈傳遞來處理請求,有時,這個請求本身帶有一個接受請求對象的引用,這時該模式就使用了托付。
B r i d g e ( 4 . 2 )?將實現和抽象分離開,假設抽象和一個特定實現很匹配,那么這個實現能夠代理抽象的操作。
托付是對象組合的特例。它告訴你對象組合作為一個代碼復用機制能夠替代繼承。
3.?
posted @ 2017-08-10 11:07 yxysuanfa 閱讀(...) 評論(...) 編輯 收藏 刷新評論刷新頁面返回頂部 Copyright ?2019 yxysuanfa總結
以上是生活随笔為你收集整理的java设计模式概述的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 共利、共济、共赢 铸就黔南大数据时代丰碑
- 下一篇: 自动化测试,从入门到放弃