模式 与 原则
模式:每一個模式描述了一個在我們周圍不斷重復發生的問題,以及該問題的解決方案的核心。
比設計模式更重要:GRASP (職責分配原則)
要學習設計模式,有些基礎知識是我們必須要先知道的,設計模式是關于類和對象的一種高效、靈活的使用方式,也就是說,必須先有類和對象,才能有設計模式的用武之地,否則一切都是空談,那么類和對象是從那冒出來的呢?這時就需要比23種設計模式更重要更經典的GRASP模式登場了,嘿嘿,原來這才是老大!
GRASP(General Responsibility Assignment Software Patterns),中文名稱為“通用職責分配軟件模式”,GRASP一共包括9種模式,它們描述了對象設計和職責分配的基本原則。也就是說,如何把現實世界的業務功能抽象成對象,如何決定一個系統有多少對象,每個對象都包括什么職責,GRASP模式給出了最基本的指導原則。初學者應該盡快掌握、理解這些原則,因為這是如何設計一個面向對象系統的基礎。可以說,GRASP是學習使用設計模式的基礎。
1.????? Information Expert (信息專家)
????????2.????? Creator (創造者)
???????3.????? Low coupling (低耦合)
????????4.????? High cohesion (高內聚)
????????5.????? Controller (控制器)
????????6.????? Polymorphism (多態)
????????7.????? Pure Fabrication (純虛構)
????????8.????? Indirection (間接)
????????9.????? Protected Variations (受保護變化)
1.????? Information Expert (信息專家)
信息專家模式是面向對象設計的最基本原則,是我們平時使用最多,應該跟我們的思想融為一體的原則。也就是說,我們設計對象(類)的時候,如果某個類擁有完成某個職責所需要的所有信息,那么這個職責就應該分配給這個類來實現。這時,這個類就是相對于這個職責的信息專家。
例如:常見的網上商店里的購物車(ShopCar),需要讓每種商品(SKU)只在購物車內出現一次,購買相同商品,只需要更新商品的數量即可。如下圖:
?
?
針對這個問題需要權衡的是,比較商品是否相同的方法需要放到那里類里來實現呢?分析業務得知需要根據商品的編號(SKUID)來唯一區分商品,而商品編號是唯一存在于商品類里的,所以根據信息專家模式,應該把比較商品是否相同的方法放在商品類里。??
2.????? Creator (創造者)
實際應用中,符合下列任一條件的時候,都應該由類A來創建類B,這時A是B的創建者:
a.?????? A是B的聚合
b.?????? A是B的容器
c.?????? A持有初始化B的信息(數據)
d.?????? A記錄B的實例
e.?????? A頻繁使用B
如 果一個類創建了另一個類,那么這兩個類之間就有了耦合,也可以說產生了依賴關系。依賴或耦合本身是沒有錯誤的,但是它們帶來的問題就是在以后的維護中會產 生連鎖反應,而必要的耦合是逃不掉的,我們能做的就是正確地創建耦合關系,不要隨便建立類之間的依賴關系,那么該如何去做呢?就是要遵守創建者模式規定的 基本原則,凡是不符合以上條件的情況,都不能隨便用A創建B。
例如:因為訂單(Order)是商品(SKU)的容器,所以應該由訂單來創建商品。如下圖:
?
?
?????? 這里因為訂單是商品的容器,也只有訂單持有初始化商品的信息,所以這個耦合關系是正確的且沒辦法避免的,所以由訂單來創建商品。??
3.????? Low coupling (低耦合)
低耦合模式的意思就是要我們盡可能地減少類之間的連接。
其作用非常重要:
a.?????? 低耦合降低了因一個類的變化而影響其他類的范圍。
b.?????? 低耦合使類更容易理解,因為類會變得簡單,更內聚。
下面這些情況會造成類A、B之間的耦合:
a.?????? A是B的屬性
b.?????? A調用B的實例的方法
c.?????? A的方法中引用了B,例如B是A方法的返回值或參數。
d.?????? A是B的子類,或者A實現了B
關于低耦合,還有下面一些基本原則:
a.?????? Don’t Talk to Strangers原則:
意思就是說,不需要通信的兩個對象之間,不要進行無謂的連接,連接了就有可能產生問題,不連接就一了百了啦!
b.?????? 如果A已經和B有連接,如果分配A的職責給B不合適的話(違反信息專家模式),那么就把B的職責分配給A。
c.?????? 兩個不同模塊的內部類之間不能直接連接,否則必招報應!嘿!
例如:Creator模式的例子里,實際業務中需要另一個出貨人來清點訂單(Order)上的商品(SKU),并計算出商品的總價,但是由于訂單和商品之間的耦合已經存在了,那么把這個職責分配給訂單更合適,這樣可以降低耦合,以便降低系統的復雜性。如下圖:
?
?
?????? 這里我們在訂單類里增加了一個TotalPrice()方法來執行計算總價的職責,沒有增加不必要的耦合。?
????????????? 4.????? High cohesion (高內聚)
高內聚的意思是給類盡量分配內聚的職責,也可以說成是功能性內聚的職責。即功能性緊密相關的職責應該放在一個類里,并共同完成有限的功能,那么就是高內聚合。這樣更有利于類的理解和重用,也便于類的維護。
高內聚也可以說是一種隔離,就想人體由很多獨立的細胞組成,大廈由很多磚頭、鋼筋、混凝土組成,每一個部分(類)都有自己獨立的職責和特性,每一個部分內部發生了問題,也不會影響其他部分,因為高內聚的對象之間是隔離開的。
例如:一個訂單數據存取類(OrderDAO),訂單即可以保存為Excel模式,也可以保存到數據庫中;那么,不同的職責最好由不同的類來實現,這樣才是高內聚的設計,如下圖:
?
?
?????? 這里我們把兩種不同的數據存儲功能分別放在了兩個類里來實現,這樣如果未來保存到Excel的功能發生錯誤,那么就去檢查OrderDAOExcel類就可以了,這樣也使系統更模塊化,方便劃分任務,比如這兩個類就可以分配個不同的人同時進行開發,這樣也提高了團隊協作和開發進度。??
?
5.????? Controller (控制器)
用來接收和處理系統事件的職責,一般應該分配給一個能夠代表整個系統的類,這樣的類通常被命名為“XX處理器”、“XX協調器”或者“XX會話”。
關于控制器類,有如下原則:
a.?????? 系統事件的接收與處理通常由一個高級類來代替。
b.?????? 一個子系統會有很多控制器類,分別處理不同的事務。
關于這個模式更詳細的論述,請參考《UML和模式應用》第16章。???
?
6.????? Polymorphism (多態)
這里的多態跟OO三大基本特征之一的“多態”是一個意思。
例如:我們想設計一個繪圖程序,要支持可以畫不同類型的圖形,我們定義一個抽象類Shape,矩形(Rectangle)、圓形(Round)分別繼承這個抽象類,并重寫(override)Shape類里的Draw()方法,這樣我們就可以使用同樣的接口(Shape抽象類)繪制出不同的圖形,如下圖:
?
?
這樣的設計更符合高內聚和低耦合原則,雖然后來我們又增加了一個菱形(Diamond)類,對整個系統結構也沒有任何影響,只要增加一個繼承Shape的類就行了。??
?
7.????? Pure Fabrication (純虛構)
這 里的純虛構跟我們常說的純虛構函數意思相近。高內聚低耦合,是系統設計的終極目標,但是內聚和耦合永遠都是矛盾對立的。高內聚以為這拆分出更多數量的類, 但是對象之間需要協作來完成任務,這又造成了高耦合,反過來毅然。該如何解決這個矛盾呢,這個時候就需要純虛構模式,由一個純虛構的類來協調內聚和耦合, 可以在一定程度上解決上述問題。
例如:上面多態模式的例子,如果我們的繪圖程序需要支持不同的系統,那么因為不同系統的API結構不同,繪圖功能也需要不同的實現方式,那么該如何設計更合適呢?如下圖:
?
?
這里我們可以看到,因為增加了純虛構類AbstractShape,不論是哪個系統都可以通過AbstractShape類來繪制圖形,我們即沒有降低原來的內聚性,也沒有增加過多的耦合,可謂魚肉和熊掌兼得,哈哈哈!?
??????? 8.????? Indirection (間接)
“間 接”顧名思義,就是這個事不能直接來辦,需要繞個彎才行。繞個彎的好處就是,本來直接會連接在一起的對象彼此隔離開了,一個的變動不會影響另一個。就想我 在前面的低耦合模式里說的一樣,“兩個不同模塊的內部類之間不能直接連接”,但是我們可以通過中間類來間接連接兩個不同的模塊,這樣對于這兩個模塊來說, 他們之間仍然是沒有耦合/依賴關系的。
?
9.????? Protected Variations (受保護變化)
預先找出不穩定的變化點,使用統一的接口封裝起來,如果未來發生變化的時候,可以通過接口擴展新的功能,而不需要去修改原來舊的實現。也可以把這個模式理解為OCP(開閉原則)原則,就是說一個軟件實體應當對擴展開發,對修改關閉。在設計一個模塊的時候,要保證這個模塊可以在不需要被修改的前提下可以得到擴展。這樣做的好處就是通過擴展給系統提供了新的職責,以滿足新的需求,同時又沒有改變系統原來的功能。關于OCP原則,后面還會有單獨的論述。
?
?
?
比設計模式更重要:設計原則
1. 單一職責原則(SRP)
2. 開放—封閉原則(OCP)
3. 依賴倒置原則(DIP)
4. 接口隔離原則(ISP)
5. 替換原則(LSP)
?
我 們生活在一個充滿規則的世界里,在復雜多變的外表下,萬事萬物都被永恒的真理支配并有規律的運行著。模式也是一樣,不論那種模式,其背后都潛藏著一些“永 恒的真理”,這個真理就是設計原則。記得一次參加微軟的架構師培訓,期間講到設計模式,有人問了老師一個問題:“什么東西比設計模式更重要?”,老師是一 位有多年豐富實踐經驗的開發者,他毫不猶豫地回答到:“比模式更重要的是原則”。這句話我時常能夠想起,越來越覺得這是一個偉大的答案。的確,還有什么比 原則更重要呢?就像人的世界觀和人生觀一樣,那才是支配你一切行為的根本,而對于設計模式來說,為什么這個模式要這樣解決這個問題,而另一個模式要那樣, 它們背后都遵循的就是永恒的設計原則。可以說,設計原則是設計模式的靈魂。
對于設計原則的深入探討我還沒有那個深度,推薦大家去看《敏捷軟件開發—原則、模式與實踐》,下面僅對部分常用的設計原則做些簡單的講解:
?? 1. 單一職責原則(SRP)
?? “就一個類而言,應該僅有一個引起它變化的原因。”也就是說,不要把變化原因各不相同的職責放在一起,因為不同的變化會影響到不相干的職責。再通俗一點地說就是,不該你管的事情你不要管,管好自己的事情就可以了,多管閑事害了自己也害了別人。(當然這里說的多管閑事跟見義勇為是兩回事,我們提倡見義勇為!)
?????? 例如:參考下圖中的設計,圖形計算程序只使用了正方形的Area()方法,永遠不會使用Draw()方法,而它卻跟Draw方法關聯了起來。這違反了單一原則,如果未來因為圖形繪制程序導致Draw()方法產生了變化,那么就會影響到本來毫不關系的圖形計算程序。
?
?
????????? ??那么我們該怎么做呢?如下圖,將不同的職責分配給不同的類,使單個類的職責盡量單一,就隔離了變化,這樣他們也不會互相影響了。
?
?
????????????? 2. 開放—封閉原則(OCP)
? ?“軟件實體(類、模塊、函數等)應該是可以擴展的,但是不可修改。”嘿!多么樸實的話語,第一次看這個原則的時候我都看傻了,我當時在想“這不是&#%做白日夢嗎!不修改怎么擴展啊?”但是隨著學習的深入,理解了這個“不修改”是什么意思,意思是“你可以隨便增加新的類,但是不要修改原來的類”。從這個角度去理解就好多了,其實這里還是一個隔離變化的問題。
?????? 例如:如下圖,有一個客戶端程序通過數據訪問接口操作數據,對于這套系統來說,一開始計劃使用的是SQL Server或Oracle數據庫,但是后來考慮到成本,改用免費的MySQL;那么對于客戶端程序來說,后來數據的擴展對它沒有任何影響,它在不知不覺間就用上了免費好用的MySQL數據庫,這全要感謝OCP原則。
?
?
????? 3. 依賴倒置原則(DIP)
“抽象不應該依賴于細節。細節應該依賴于抽象。”關于這個原則,還有種說法是.“高層不應該依賴于底層,兩者都應該依賴于抽象。”其實怎么說都是對的,關鍵就是要理解一點,只有抽象的東西才是最穩定的,也就是說,我們依賴的是它的穩定。如果將來“抽象”也不穩定了,那么誰穩定我跟誰,其實說白了不就是傍大款嗎!哈哈!
例如:參考下圖的設計,一個開關跟燈直接連接在一起了,也就是說開關依賴于燈的打開和關閉方法,那么如果我想用這個開關也可以打開其他東西呢,比如電視、音響。顯然這個設計是無法滿足這個要了,因為我們依賴了細節而不是抽象,這個開關已經等價于“燈的開關”。
?
?
?????? 那么我們該如何來設計一個通用的開關呢?參考下圖的設計,OK!現在我們不僅可以打開燈,還可以打開電視和音響,甚至未來任何實現了“開關接口”的任何東西。
?
?
?
???????????? 4. 接口隔離原則(ISP)
“不應該強迫客戶依賴于它們不用的方法。接口屬于客戶,不屬于它所在的類層次結構。”這個說得很明白了,再通俗點說,不要強迫客戶使用它們不用的方法,如果強迫用戶使用它們不使用的方法,那么這些客戶就會面臨由于這些不使用的方法的改變所帶來的改變。
?例如:參考下圖的設計,在這個設計里,取款、存款、轉帳都使用一個通用界面接口,也就是說,每一個類都被強迫依賴了另兩個類的接口方法,那么每個類有可能因為另外兩個類的方法(跟自己無關)而被影響。拿取款來說,它根本不關心“存款操作”和“轉帳操作”,可是它卻要受到這兩個方法的變化的影響,真是土鱉!!!
?
?
????????那么我們該如何解決這個問題呢?參考下圖的設計,為每個類都單獨設計專門的操作接口,使得它們只依賴于它們關系的方法,這樣就不會互相影響,也就不會在發生土鱉的事情了!
?
?
????????????5. 替換原則(LSP)
“子類型必須能夠替換掉它們的基類型。”也就是說繼承中的“IS A”關系是必須保證的,否則還算什么繼承啊!如果違反了LSP原則,常會導致在運行時(RTTI)的類型判斷違反OCP原則。
例如:函數A的參數是基類型,調用時傳遞的對象是子類型,正常情況下,增加子類型都不會影響到函數A的,如果違反了LSP,則函數A必須小心的判斷傳進來的具體類型,否則就會出錯,這就已經違反了OCP原則。
關于模式學習
深刻理解面向對象是學好設計模式的基礎,掌握一定的面向對象設計原則才能掌握面向對象設計模式的精髓,從而實現靈活運用設計模式。僅知道OO的語言機制是不夠的,懂得語言里的封裝、繼承、多態,只是滿足了最最基礎的條件,要真正發揮OO的強大的作用,關鍵是要深刻理解以上的GRASP模式和設計原則,在此基礎上去再深入理解設計模式,并在實踐中不斷磨練。
模式跟OO原則相比其實并不重要,如果你能設計出基本符合以上原則的程序,那么可能就已經總結出了新的模式,所以學習模式的根本是為了深入理解OO思想和原則,使我們可以寫出高內聚低耦合的程序。
另外最近在學習李建忠老師的“C#面向對象設計模式縱橫談系列課程”時候,李老師提出了一個“重構到模式”的理論,感覺十分有道理,模式不完全是供我們套用的模版,在特定的業務環境下,我們實現的可能只是“類似XX模式”的設計模式,因為針對這個環境,這么使用就是最合適的,而不是什么時候都必須完全照搬GOF的23種設計模式的格式,模式是死的,而人是活的,找到最合適的實現方式就好,不要為了設計模式而使用設計模式。
轉載于:https://www.cnblogs.com/tecs27/archive/2012/03/13/2394138.html
總結
- 上一篇: 本科生学习fpga,dsp,嵌入式操作系
- 下一篇: SQL数据基本操作