軟體設計模式

軟體設計模式

軟體設計模式(Design pattern)是對給定的應用環境中典型軟體設計問題經過多次檢驗的解決方案的描述,是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼的可靠性、程序的重用性。

簡介


設計模式(英語 design pattern)是對面向對象設計中反覆出現的問題的解決方案。這個術語是在1990年代由Erich Gamma等人從建築設計領域引入到計算機科學中來的。這個術語的含義還存有爭議。演演算法不是設計模式,因為演演算法致力於解決問題而非設計問題。設計模式通常描述了一組相互緊密作用的類與對象。設計模式提供一種討論軟體設計的公共語言,使得熟練設計者的設計經驗可以被初學者和其他設計者掌握。設計模式還為軟體重構提供了目標。
隨著軟體開發社群對設計模式的興趣日益增長,已經出版了一些相關的專著,定期召開相應的研討會,而且Ward Cunningham為此發明了WikiWiki用來交流設計模式的經驗。

歷史


肯特·貝克和沃德·坎寧安在1987年利用克里斯托佛·亞歷山大在建築設計領域裡的思想開發了設計模式並把此思想應用在Smalltalk中的圖形用戶介面的生成中。一年後Erich Gamma在他的蘇黎世大學博士畢業論文中開始嘗試把這種思想改寫為適用於軟體開發。於此同時James Coplien 在1989年至1991 年也在利用相同的思想致力於C++的開發,而後於1991年發表了他的著作Advanced C++ Idioms。就在這一年Erich Gamma 得到了博士學位,然後去了美國,在那與Richard Helm, Ralph Johnson ,John Vlissides合作出版了Design Patterns - Elements of Reusable Object-Oriented Software 一書,在此書中共收錄了23個設計模式。這四位作者在軟體開發領域裡也以他們的匿名著稱Gang of Four(四人幫,簡稱GoF),並且是他們在此書中的協作導致了軟體設計模式的突破。有時這個匿名GoF也會用於指代前面提到的那本書。

模式格式


儘管名稱和順序在不同的資料中各有不同,描述模式的格式大致分為以下四個主要部分:
模式名稱(Pattern Name):每一個模式都有自己的名字,模式的名字使得我們可以討論我們的設計。
問題(Problem):在面向對象的系統設計過程中反覆出現的特定場合,它導致我們採用某個模式。
解決方案(Solution):上述問題的解決方案,其內容給出了設計的各個組成部分,它們之間的關係、職責劃分和協作方式。
效果(Consequence):採用該模式對軟體系統其他部分的影響,比如對系統的擴充性、可移植性的影響。影響也包括負面的影響。
別名(Also Known As):一個模式可以有超過一個以上的名稱。這些名稱應該要在這一節註明。
動機(Motivation):該模式應該利用在哪種情況下是本節提供的方案(包括問題與來龍去脈)的責任。
應用(Applicability)
結構(Structure):這部分常用類圖與互動圖闡述此模式。
參與者(Participants):這部分提供一份本模式用到的類與物件清單,與它們在設計下扮演的角色。
合作(Collaboration):描述在此模式下,類與物件間的互動。
結果(Consequences):這部分應描述使用本模式後的結果、副作用、與交換(trade-off)
實現(Implementaion):這部分應描述實現該模式、該模式的部分方案、實現該模式的可能技術、或者建議實現模式的方法。
常式(Sample Code):示範程式。
已知應用(Known Uses):業界已知的實做範例。
相關模式(Related Patterns):這部分包括其他相關模式,以及與其他類似模式的不同。

相近術語


對某個問題經常出現的、在設計中應該盡量避免的、壞的設計方案被稱為反模式。

模式列表

基礎模式
委託模式
介面模式
代理模式

創建模式

抽象工廠模式(Abstract Factory) ,提供一個創建一系列相關或相互依賴對象的介面,而無需指定它們具體的類。
生成器模式 (Builder),將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
工廠方法模式(Factory Method) ,定義一個用於創建對象的介面,讓子類決定將哪一個類實例化。Factory Method使一個類的實例化延遲到其子類。
原型模式 (Prototype) ,用原型實例指定創建對象的種類,並且通過拷貝這個原型來創建新的對象。
單例模式(Singleton),保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

結構模式

適配器模式 (Adapter) ,將一個類的介面轉換成客戶希望的另外一個介面。Adapter模式使得原本由於介面不兼容而不能一起工作的那些類可以一起工作。
橋接模式(Bridge) ,將抽象部分與它的實現部分分離,使它們都可以獨立地變化。
組合模式(Composite) ,將對象組合成樹形結構以表示“部分-整體”的層次結構。它使得客戶對單個對象和複合對象的使用具有一致性。
容器模式
修飾模式 (Decorator) ,動態地給一個對象添加一些額外的職責。就擴展功能而言,它比生成子類方式更為靈活。
擴展性模式
外觀模式
享元模式
管道與過濾器模式
代理模式(Proxy) ,為其他對象提供一個代理以控制對這個對象的訪問。

行為模式

責任鏈模式 (Chain of Responsibility) ,為解除請求的發送者和接收者之間耦合,而使多個對象都有機會處理這個請求。將這些對象連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個對象處理它。
命令模式 (Command) ,將一個請求封裝為一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支持可取消的操作。
柯里化模式
事件監聽器模式
解釋器模式
迭代器模式
中介者模式
備忘錄模式 (Memento) ,在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。這樣以後就可將該對象恢復到保存的狀態。
觀察者模式(Observer) ,定義對象間的一種一對多的依賴關係,以便當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並自動刷新。
狀態模式 (State) ,允許一個對象在其內部狀態改變時改變它的行為。對象看起來似乎修改了它所屬的類。
策略模式 (Strategy) ,定義一系列的演演算法,把它們一個個封裝起來, 並且使它們可相互替換。本模式使得演演算法的變化可獨立於使用它的客戶。
模板方法模式
訪問者模式 (Visitor),表示一個作用於某對象結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。
層次訪問者模式

併發模式

模式 Action at a distance
模式 Balking
模式 Guarded suspension
模式 Scheduler
模式 Read write lock
模式 Double checked locking
模式 Disable job requests while running job

實時模式

模式 Scheduled task
模式 User interface
模式 Disable job requests while running job

其他

模型—視圖—控制器模式

模式原則


大家都開始注意設計模式。那麼,到底我們為什麼要用設計模式呢?這麼多設計模式為什麼要怎麼設計呢?說實話,以前我還真沒搞清楚。就是看大家一口一個"Design pattern",心就有點發虛。於是就買了本"四人幫"的設計模式,結果看得似懂非懂:看得時候好像是懂了,過一會就忘了。可能是本人比較"愚鈍"吧:))最近,有了點感悟。"獨樂不如眾樂",與大家分享一下,還望指教! 為什麼要提倡"Design Pattern"呢?根本原因是為了代碼復用,增加可維護性。那麼怎麼才能實現代碼復用呢?OO界有前輩的幾個原則:"開-閉"原則(Open Closed Principal)、里氏代換原則、合成復用原則。設計模式就是實現了這些原則,從而達到了代碼復用、增加可維護性的目的。

開-閉原則

此原則是由"Bertrand Meyer"提出的。原文是:"Software entities should be open for extension,but closed for modification"。就是說模塊應對擴展開放,而對修改關閉。模塊應盡量在不修改原(是"原",指原來的代碼)代碼的情況下進行擴展。那麼怎麼擴展呢?我們看工廠模式"factory pattern":假設中關村有一個賣盜版盤和毛片的小子,我們給他設計一"光碟銷售管理軟體"。我們應該先設計一"光碟"介面。如圖:【pre】______________|<>|| 光碟 ||_____________||+賣() || ||_____________|【/pre】而盜版盤和毛片是其子類。小子通過"DiscFactory"來管理這些光碟。代碼為:
有人要買盜版盤,怎麼實現呢?
public class 小子{ public static void main(String【】 args){ 光碟 d=DiscFactory.getDisc("盜版盤"); 光碟。賣(); } }
如果有一天,這小子良心發現了,開始賣正版軟體。沒關係,我們只要再創建一個"光碟"的子類"正版軟體"就可以了。不需要修改原結構和代碼。怎麼樣?對擴展開放,對修改關閉。"開-閉原則" 工廠模式是對具體產品進行擴展,有的項目可能需要更多的擴展性,要對這個"工廠"也進行擴展,那就成了"抽象工廠模式"。

里氏代換原則

里氏代換原則是由"Barbara Liskov"提出的。如果調用的是父類的話,那麼換成子類也完全可以運行。比如:光碟 d=new 盜版盤(); d.賣(); 要將"盜版盤"類改為"毛片"類,沒問題,完全可以運行。Java編譯程序會檢查程序是否符合里氏代換原則。還記得java繼承的一個原則嗎?子類override方法的訪問許可權不能小於父類對應方法的訪問許可權。比如"光碟"中的方法"賣"訪問許可權是"public",那麼"盜版盤"和"毛片"中的"賣"方法就不能是protected或private,編譯不能通過。為什麼要這樣呢?你想啊:如果"盜版盤"的"賣"方法是private。那麼下面這段代碼就不能執行了:光碟 d=new 盜版盤(); d.賣();可以說:里氏代換原則是繼承復用的一個基礎。

合成復用原則

就是說要少用繼承,多用合成關係來實現。我曾經這樣寫過程序:有幾個類要與資料庫打交道,就寫了一個資料庫操作的類,然後別的跟資料庫打交道的類都繼承這個。結果後來,我修改了資料庫操作類的一個方法,各個類都需要改動。"牽一髮而動全身"!面向對象是要把波動限制在盡量小的範圍。
在Java中,應盡量針對Interface編程,而非實現類。這樣,更換子類不會影響調用它方法的代碼。要讓各個類儘可能少的跟別人聯繫,"不要與陌生人說話"。這樣,城門失火,才不至於殃及池魚。擴展性和維護性才能提高
理解了這些原則,再看設計模式,只是在具體問題上怎麼實現這些原則而已。張無忌太極拳,忘記了所有招式,打倒了"玄冪二老",所謂"心中無招"。設計模式可謂招數,如果先學通了各種模式,又忘掉了所有模式而隨心所欲,可謂OO之最高境界。呵呵,搞笑,搞笑!(JR)
依賴倒轉原則抽象不應該依賴與細節,細節應當依

依賴倒轉原則

要針對介面編程,而不是針對實現編程。傳遞參數,或者在組合聚合關係中,盡量引用層次高的類。主要是在構造對象時可以動態的創建各種具體對象,當然如果一些具體類比較穩定,就不必在弄一個抽象類做它的父類,這樣有畫舌添足的感覺介面隔離原則定製服務的例子,每一

介面隔離原則

一種角色,不多不少,不幹不該乾的事,該乾的事都要干 抽象類抽象類不會有實例

抽象類

類為子類繼承,一般包含這個系的共同屬性和方法。注意:好的繼承關係中,只有葉節點是具體類,其他節點應該都是抽象類,也就是說具體類是不被繼承的。將儘可能多的共同代碼放到抽象類中。 7 迪米特法則最少知識原則。不要和陌生人說話。

四個要素


設計模式使人們可以更加簡單方便地復用成功的設計和體系結構。將已證實的技術表述成設計模式也會使新系統開發者更加容易理解其設計思路。
模式名稱
一個助記名,它用一兩個詞來描述模式的問題、解決方案和效果。命名一個新的模式增加了我們的設計辭彙。設計模式允許我們在較高的抽象層次上進行設計。基於一個模式辭彙表,我們自己以及同事之間就可以討論模式並在編寫文檔時使用它們。模式名可以幫助我們思考,便於我們與其他人交流設計思想及設計結果。找到恰當的模式名也是我們設計模式編目工作的難點之一。
問題
描述問題存在的前因後果,它可能描述了特定的設計問題,如怎樣用對象表示演演算法等。也可能描述了導致不靈活設計的類或對象結構。有時候,問題部分會包括使用模式必須滿足的一系列先決條件。
解決方案
描述了設計的組成成分,它們之間的相互關係及各自的職責和協作方式。因為模式就像一個模板,可應用於多種不同場合,所以解決方案並不描述一個特定而具體的設計或實現,而是提供設計問題的抽象描述和怎樣用一個具有一般意義的元素組合(類或對象組合)來解決這個問題。
效果
描述了模式應用的效果及使用模式應權衡的問題。儘管我們描述設計決策時,並不總提到模式效果,但它們對於評價設計選擇和理解使用模式的代價及好處具有重要意義。軟體效果大多關注對時間和空間的衡量,它們也表述了語言和實現問題。因為復用是面向對象設計的要素之一,所以模式效果包括它對系統的靈活性、擴充性或可移植性的影響,顯式地列出這些效果對理解和評價這些模式很有幫助。