隨筆-14  評論-25  文章-1  trackbacks-0
          單一職責原則 ——SRP
          就一個類而言,應該僅有一個引起它的變化的原因
          ?
          原則

          最簡單,最單純的事情最容易控制,最有效
          類的職責簡單而且集中,避免相同的職責分散到不同的類之中,避免一個類承擔過多的職責
          減少類之間的耦合
          當需求變化時,只修改一個地方

          組件

          每個組件集中做好一件事情
          組件的顆粒度
          發(fā)布的成本
          可重用的成本
          ?
          方法

          避免寫臃腫的方法
          Extract Method
          ?
          重構

          Move Field/Move Class
          Extract Method/Extract Class
          ?
          最簡單的,也是最難以掌握的原則
          ?
          實例分析


          單一職責很容易理解,也很容易實現(xiàn)。所謂單一職責,就是一個設計元素只做一件事。什么是“只做一件事”?簡單說就是少管閑事。現(xiàn)實中就是如此,如果要你專心做一件事情,任何人都有信心可以做得很出色。但如果,你整天被亂七八糟的事所累,還有心思和精力把每件事都作好么?
          fig-1.JPG
          ????? “單一職責”就是要在設計中為每種職責設計一個類,彼此保持正交,互不干涉。這個雕塑(二重奏)就是正交的一個例子,鋼琴家和小提琴家各自演奏自己的樂 譜,而結果就是一個和諧的交響樂。當然,真實世界中,演奏小提琴和彈鋼琴的必須是兩個人,但是在軟件中,我們往往會把兩者甚至更多攪和到一起,很多時候只 是為了方便或是最初設計的時候沒有想到。?

          ??????這樣的例子在設計中很常見,書中就給了一個很好的例子:調制解調器。這是一個調制 解調器最基本的功能。但是這個類事實上完成了兩個職責:連接的建立和中斷、數(shù)據(jù)的發(fā)送和接收。顯然,這違反了SRP。這樣做會有潛在的問題:當僅需要改變 數(shù)據(jù)連接方式時,必須修改Modem類,而修改Modem類的結果就是使得任何依賴Modem類的元素都需要重新編譯,不管它是不是用到了數(shù)據(jù)連接功能。 解決的辦法,書中也已經給出:重構Modem類,從中抽出兩個接口,一個專門負責連接、另一個專門負責數(shù)據(jù)發(fā)送。依賴Modem類的元素也要做相應的細 化,根據(jù)職責的不同分別依賴不同的接口。最后由ModemImplementation類實現(xiàn)這兩個接口。
          fig-2.JPG ??????從這個例子中,我們不難發(fā)現(xiàn),違反SRP通常是由于過于“真實”地設計了一個類所造成的。因此,解決辦法是往更高一層進行抽象 化提取,將對某個具體類的依賴改變?yōu)閷σ唤M接口或抽象類的依賴。當然,這個抽象化的提取應該根據(jù)需要設計,而不是盲目提取。比如剛才這個Modem的例子 中,如果有必要,還可以把DataChannel抽象為DataSender和DataReceiver兩個接口。


          開放封閉原則 ——OCP
          軟件實體(類,模塊,函數(shù))應該是可以擴展的,但是不可修改的
          ?
          原則
          ?
          對擴展是開放的,當需求改變時我們可以對模塊進行擴展,使其具有新的功能
          對更改是封閉的,對模塊擴展時,不需要改動原來的代碼
          面對抽象而不是面對細節(jié),抽象比細節(jié)活的更長
          僵化的設計——如果程序中一處改動產生連鎖反應。

          方法

          條件case?? if/else 語句
          ?
          重構

          Replace Type Code With Class
          Replace Type Code With State/Strategy
          Replace Conditional with polymorphism
          ?
          實例

          開閉原則很簡單,一句話:“Closed for Modification; Open for Extension”——“對變更關閉;對擴展開放”。開閉原則其實沒什么好講的,我將其歸結為一個高層次的設計總則。就這一點來講,OCP的地位應該比SRP優(yōu)先。

          OCP的動機很簡單:軟件是變化的。不論是優(yōu)質的設計還是低劣的設計都無法回避這一問題。OCP說明了軟件設計應該盡可能地使架構穩(wěn)定而又容易滿足不同的需求。

          為什么要OCP?答案也很簡單——重用。

          “重用”,并不是什么軟件工程的專業(yè)詞匯,它是工程界所共用的詞匯。早在軟件出現(xiàn)前,工程師們就在實踐“重用”了。比如機械產品,通過零部 件的組裝得到最終的能夠使用的工具。由于機械部件的設計和制造過程是極其復雜的,所以互換性是一個重要的特性。一輛車可以用不同的發(fā)動機、不同的變速箱、 不同的輪胎……很多東西我們直接買來裝上就可以了。這也是一個OCP的例子。(可能是由于我是搞機械出身的吧,所以就舉些機械方面的例子^_^)。

          如何在OO中引入OCP原則?把對實體的依賴改為對抽象的依賴就行了。下面的例子說明了這個過程:

          05賽季的時候,一輛F1賽車有一臺V10引擎。但是到了06賽季,國際汽聯(lián)修改了規(guī)則,一輛F1賽車只能安裝一臺V8引擎。車隊很快投入了新賽車 的研發(fā),不幸的是,從工程師那里得到消息,舊車身的設計不能夠裝進新研發(fā)的引擎。我們不得不為新的引擎重新打造車身,于是一輛新的賽車誕生了。但是,麻煩 的事接踵而來,國際汽聯(lián)頻頻修改規(guī)則,搞得設計師在“賽車”上改了又改,最終變得不成樣子,只能把它廢棄。

          OCP-fig1.JPG

          為了能夠重用這輛昂貴的賽車,工程師們提出了解決方案:首先,在車身的設計上預留出安裝引擎的位置和管線。然后,根據(jù)這些設計好的規(guī)范設計引擎(或是引擎的適配器)。于是,新的賽車設計方案就這樣誕生了。

          ?OCP-fig2.JPG

          顯然,通過重構,這里應用的是一個典型的Bridge模式。這個實現(xiàn)的關鍵之處在于我們預先給引擎留出了位置!我們不必因為對引擎的規(guī)則的頻頻變更而制造相當多的車身,而是盡可能地沿用和改良現(xiàn)有的車身。
          說到這里,想說一說OO設計的一個誤區(qū)。
          學 習OO語言的時候,為了能夠說明“繼承”(或者說“is-a”)這個概念,教科書上經常用實際生活中的例子來解釋。比如汽車是車,電車是車,F(xiàn)1賽車是汽 車,所以車是汽車、電車、F1賽車的上層抽象。這個例子并沒有錯。問題是,這樣的例子過于“形象”了!如果OO設計直接就可以將現(xiàn)實生活中的概念引用過 來,那也就不需要什么軟件工程師了!OO設計的關鍵概念是抽象。如果沒有抽象,那所有的軟件工程師的努力都是徒勞的。因為如果沒有抽象,我們只能去構造世 界中每一個對象。上面這個例子中,我們應該看到“引擎”這個抽象的存在,因為車隊的工程師們?yōu)樗A留了位置,為它制定了設計規(guī)范。
          上面這個設計也 實現(xiàn)了后面要說的DIP(依賴倒置原則)。但是請記住,OCP是OO設計原則中高層次的原則,其余的原則對OCP提供了不同程度的支持。為了實現(xiàn)OCP, 我們會自覺或者不自覺地用到其它原則或是諸如Bridge、Decorator等設計模式。然而,對于一個應用系統(tǒng)而言,實現(xiàn)OCP并不是設計目的,我們 所希望的只是一個穩(wěn)定的架構。所以對OCP的追求也應該適可而止,不要陷入過渡設計。正如Martin本人所說:“No significant program can be 100% closed.”“Closure not complete but strategic”

          Liskov替換原則—— LSP?

          子類型必須能夠替換它的基類型

          原則

          主要針對繼承的設計原則
          所有派生類的行為功能必須和客戶程序對其基類所期望的保持一致。
          派生類必須滿足基類和客戶程序的約定
          IS-A是關于行為方式的,依賴客戶程序的調用方式
          ?
          重構

          Extract Supper Class
          ?
          實例

          長方形和正方形

          OCP作為OO的高層原則,主張使用“抽象(Abstraction)”和“多態(tài)(Polymorphism)”將設計中的靜態(tài)結構改為動態(tài)結構,維持設計的封閉性。

          “抽象”是語言提供的功能。“多態(tài)”由繼承語義實現(xiàn)。

          如此,問題產生了:“我們如何去度量繼承關系的質量?”

          Liskov于1987年提出了一個關于繼承的原則“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”——“繼承必須確保超類所擁有的性質在子類中仍然成立。”也就是說,當一個子類的實例應該能夠替換任何其超類的實例時,它們之間才具有 is-A關系。

          該原則稱為Liskov Substitution Principle——里氏替換原則。林先生在上課時風趣地稱之為“老鼠的兒子會打洞”。^_^

          我們來研究一下LSP的實質。學習OO的時候,我們知道,一個對象是一組狀態(tài)和一系列行為的組合體。狀態(tài)是對象的內在特性,行為是對象的外在特性。LSP所表述的就是在同一個繼承體系中的對象應該有共同的行為特征。

          這一點上,表明了OO的繼承與日常生活中的繼承的本質區(qū)別。舉一個例子:生物學的分類體系中把企鵝歸屬為鳥類。我們模仿這個體系,設計出這樣的類和關系。

          ?lsp-fig1.jpg

          類“鳥”中有個方法fly,企鵝自然也繼承了這個方法,可是企鵝不能飛阿,于是,我們在企鵝的類中覆蓋了fly方法,告訴方法的調用者:企 鵝是不會飛的。這完全符合常理。但是,這違反了LSP,企鵝是鳥的子類,可是企鵝卻不能飛!需要注意的是,此處的“鳥”已經不再是生物學中的鳥了,它是軟 件中的一個類、一個抽象。

          有人會說,企鵝不能飛很正常啊,而且這樣編寫代碼也能正常編譯,只要在使用這個類的客戶代碼中加一句判斷就行了。但是,這就是問題所 在!首先,客戶代碼和“企鵝”的代碼很有可能不是同時設計的,在當今軟件外包一層又一層的開發(fā)模式下,你甚至根本不知道兩個模塊的原產地是哪里,也就談不 上去修改客戶代碼了。客戶程序很可能是遺留系統(tǒng)的一部分,很可能已經不再維護,如果因為設計出這么一個“企鵝”而導致必須修改客戶代碼,誰應該承擔這部分 責任呢?(大概是上帝吧,誰叫他讓“企鵝”不能飛的。^_^)“修改客戶代碼”直接違反了OCP,這就是OCP的重要性。違反LSP將使既有的設計不能封 閉!

          修正后的設計如下:

          ?lsp-fig2.jpg

          但是,這就是LSP的全部了么?書中給了一個經典的例子,這又是一個不符合常理的例子:正方形不是一個長方形。這個悖論的詳細內容能在網上找到,我就不多廢話了。

          LSP并沒有提供解決這個問題的方案,而只是提出了這么一個問題。

          于是,工程師們開始關注如何確保對象的行為。1988年,B. Meyer提出了Design by Contract(契約式設計)理論。DbC從形式化方法中借鑒了一套確保對象行為和自身狀態(tài)的方法,其基本概念很簡單:

          1. 每個方法調用之前,該方法應該校驗傳入參數(shù)的正確性,只有正確才能執(zhí)行該方法,否則認為調用方違反契約,不予執(zhí)行。這稱為前置條件(Pre-condition)。
          2. 一旦通過前置條件的校驗,方法必須執(zhí)行,并且必須確保執(zhí)行結果符合契約,這稱之為后置條件(Post-condition)。
          3. 對象本身有一套對自身狀態(tài)進行校驗的檢查條件,以確保該對象的本質不發(fā)生改變,這稱之為不變式(Invariant)。

          以上是單個對象的約束條件。為了滿足LSP,當存在繼承關系時,子類中方法的前置條件必須與超類中被覆蓋的方法的前置條件相同或者更寬松;而子類中方法的后置條件必須與超類中被覆蓋的方法的后置條件相同或者更為嚴格。

          一些OO語言中的特性能夠說明這一問題:

          • 繼承并且覆蓋超類方法的時候,子類中的方法的可見性必須等于或者大于超類中的方法的可見性,子類中的方法所拋出的受檢異常只能是超類中對應方法所拋出的受檢異常的子類。
            public?class?SuperClass{
            ????
            public?void?methodA()?throws?IOException{}
            }


            public?class?SubClassA?extends?SuperClass{
            ????
            //this?overriding?is?illegal.
            ????private?void?methodA()?throws?Exception{}
            }


            public?class?SubClassB?extends?SuperClass{
            ????
            //this?overriding?is?OK.
            ????public?void?methodA()?throws?FileNotFoundException{}
            }

          • 從Java5開始,子類中的方法的返回值也可以是對應的超類方法的返回值的子類。這叫做“協(xié)變”(Covariant)
            public?class?SuperClass?{
            ????
            public?Number?caculate(){
            ????????
            return?null;
            ????}

            }


            public?class?SubClass?extends?SuperClass{
            ????
            //only?compiles?in?Java?5?or?later.
            ????public?Integer?caculate(){
            ????????
            return?null;
            ????}

            }

          可以看出,以上這些特性都非常好地遵循了LSP。但是DbC呢?很遺憾,主流的面向對象語言(不論是動態(tài)語言還是靜態(tài)語言)還沒有加入對DbC的支持。但是隨著AOP概念的產生,相信不久DbC也將成為OO語言的一個重要特性之一。


          依賴倒置原則—— DIP?

          a:高層模塊不應依賴于底層模塊,兩者都應該依賴于抽象
          b:抽象不應該依賴于細節(jié),細節(jié)應該依賴于抽象

          ?
          原則

          如何解釋倒置
          高層依賴底層,重用變得困難,而最經常重用的就是framework和各個獨立的功能組件
          高層依賴底層,底層的改動直接反饋到高層,形成依賴的傳遞
          面向接口的編程
          ?

          實例

          Ioc模式
          DomainObject / DomianObjectDataService
          ?

          接口隔離原則—— ISP?

          使用多個專門的接口比使用單一的總接口總要好。換而言之,從一個客戶類的角度來講:一個類對另外一個類的依賴性應當是建立在最小接口上的。

          原則

          過于臃腫的接口是對接口的污染。不應該強迫客戶依賴于它們不用的方法。

          My object-oriented umbrella(摘自Design Patterns Explained)

          Let me tell you about my great umbrella. It is large enough to get into! In fact, three or four other people can get in it with me. While we are in it, staying out of the rain, I can move it from one place to another. It has a stereo system to keep me entertained while I stay dry. Amazingly enough, it can also condition the air to make it warmer or colder. It is one cool umbrella.

          My umbrella is convenient. It sits there waiting for me. It has wheels on it so that I do not have to carry it around. I don't even have to push it because it can propel itself. Sometimes, I will open the top of my umbrella to let in the sun. (Why I am using my umbrella when it is sunny outside is beyond me!)

          In Seattle, there are hundreds of thousands of these umbrellas in all kinds of colors. Most people call them cars.

          實現(xiàn)方法:
          1、?使用委托分離接口
          2、?使用多重繼承分離接口

          想到一個朋友說的話:所有的接口都只有一個方法,具體的類根據(jù)自己需要什么方法去實現(xiàn)接口

          posted on 2006-03-17 10:00 混沌中立 閱讀(443) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發(fā)表評論。


          網站導航:
           
          主站蜘蛛池模板: 无为县| 抚州市| 靖远县| 都兰县| 辽宁省| 马鞍山市| 仪陇县| 神木县| 霍邱县| 临朐县| 无为县| 滦平县| 合作市| 尉氏县| 商丘市| 鲜城| 金阳县| 永定县| 香港 | 渝北区| 邮箱| 天等县| 丰县| 石门县| 琼中| 宝丰县| 拉孜县| 巴里| 林周县| 侯马市| 蒲江县| 定兴县| 磐石市| 简阳市| 峨山| 喀什市| 柏乡县| 张家口市| 宁武县| 孝义市| 泽普县|