面向?qū)ο笃叽笤瓌t——肖文斌_第1頁
面向?qū)ο笃叽笤瓌t——肖文斌_第2頁
面向?qū)ο笃叽笤瓌t——肖文斌_第3頁
面向?qū)ο笃叽笤瓌t——肖文斌_第4頁
面向?qū)ο笃叽笤瓌t——肖文斌_第5頁
已閱讀5頁,還剩10頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

1、1. 單一職責原則(SRP)單一職責原則(SRP),就一個類而言,應(yīng)該僅有一個引起它變化的原因。也就是說,不要把變化原因各不相同的職責放在一起,因為不同的變化會影響到不相干的職責。再通俗一點地說就是,不該你管的事情你不要管,管好自己的事情就可以了,多管閑事害了自己也害了別人。在軟件設(shè)計中,如果一個類承擔的職責過多,就等于吧這些職責耦合在一起,而一個職責的變化可能會削弱和抑制這個類完成其他職責的能力。這耦合會導致脆弱的設(shè)計,當變化發(fā)生時,設(shè)計會遭受到意想不到的破壞。軟件設(shè)計真正要做的許多內(nèi)容,就是發(fā)現(xiàn)職責并把那些職責相互分離。如果多于一個的動機去改變一個類,那么這個類就具有多余一個的職責,就應(yīng)該

2、要考慮類的職責分離。2. 開放-封閉原則(The Open-Closed Principle 簡稱OCP)開放-封閉原則,或叫開-閉原則,是說軟件實體(類、模塊、函數(shù)等)應(yīng)該是可以擴展的,但是不可修改。不修改的意思就是是“你可以隨便增加新的類,但是不要修改原來的類”。從這個角度去理解就好多了,其實這里還是一個隔離變化的問題。這個原則的兩個特征:一個是對于擴展是開放的;另一個是對于更改是封閉的。我們在設(shè)計開發(fā)任何系統(tǒng)時,都不可能指望系統(tǒng)一開始就需求確定,就不再變化(要這樣就太幸福了,哈哈),這是不現(xiàn)實的也是不科學的想法。既然需求是有一定變化的,那么如何在面對需求變化時,設(shè)計的程序可以相對容易的修

3、改,不至于說,新需求一來,就要把整個程序推倒重來(這樣會讓程序員瘋了不可,哈哈,你不想瘋吧)。怎樣的設(shè)計才能面對需求的改變卻可以保持相對穩(wěn)定,從而使得系統(tǒng)可以在第一個版本以后不斷推出的新版本呢?開放-封閉原則就是我們的答案。在程序設(shè)計時,我們要時刻考慮盡量把類設(shè)計的足夠好,寫好了就不要去修改,如果有新的需求來了,我們增加一些類來完成新的需求,原來的代碼能不動就不動。絕對的對修改關(guān)閉是不可能的,無論模塊是多么的封閉,都會存在一些無法對之封閉的變化,既然不能完全封閉,設(shè)計人員必須對他設(shè)計的模塊應(yīng)該對那種變化封閉做出抉擇、他必須事先猜測出最有可能發(fā)生變化的變化種類,然后構(gòu)建抽象來隔離那些變化。開放-

4、封閉原則是面向?qū)ο笤O(shè)計的核心所在。遵循這個原則可以帶來面向?qū)ο蠹夹g(shù)所生成的巨大好處,也就是可維護、可擴展、可復用、靈活性好。開發(fā)人員應(yīng)該僅對程序中呈現(xiàn)出頻繁變化的部分都做出抽象,然后,對于應(yīng)用程序中的每部分都刻意定進行抽象同樣不是一個好主意,拒絕不成熟的抽象和抽象本身一樣重要。3. 依賴倒轉(zhuǎn)原則(DIP Dependency Inversion Principle)依賴倒轉(zhuǎn)原則:抽象不應(yīng)該依賴于細節(jié)。細節(jié)應(yīng)該依賴于抽象;高層不應(yīng)該依賴于底層,兩者都應(yīng)該依賴于抽象。說白了就是要針對接口編程,不要針對實現(xiàn)編程。抽象的東西才是最穩(wěn)定的,也就是說,我們依賴的是它的穩(wěn)定。依賴倒轉(zhuǎn)其實可以說是面向?qū)ο笤O(shè)計

5、的標志,用哪種語言來編寫程序不重要,如果編寫是考慮的都是如何針對抽象編程而不是針對細節(jié)編程,即程序中的所有的依賴關(guān)系都是終止與抽象類或者接口,那就是面向?qū)ο蟮脑O(shè)計,反之就是過程化設(shè)計了。4. 里氏代換原則里氏代換原則(LSP):子類型必須能夠替換掉他的父類型。說白了就是一個軟件實體如果使用的是一個父類的話,那么一定適用于其子類,而他察覺不出父類對象和子類對象的區(qū)別,也就是說,在軟件里面,把父類都替換成他的子類,程序行為沒有變化。有了里氏替換原則,才是繼承復用成為可能,只有當子類可以替換掉父類時,軟件的功能不受到影響,父類才能真正被復用,而子類也能夠在父類的基礎(chǔ)上增加新的行為。有了里氏代換原則,

6、才能使開放-封閉原則成為可能,正是由于子類型的可替換性才使得父類型的模塊在無需修改的情況下擴展。5. 接口隔離原則(ISP)接口隔離原則(ISP):不應(yīng)該強迫客戶依賴于它們不用的方法。接口屬于客戶,不屬于它所在的類層次結(jié)構(gòu)。這個說得很明白了,再通俗點說,不要強迫客戶使用它們不用的方法,如果強迫用戶使用它們不使用的方法,那么這些客戶就會面臨由于這些不使用的方法的改變所帶來的改變。6. 迪米特法則(LoD)迪米特法則(Law of Demeter或簡寫LoD)又叫最少知識原則(Least Knowledge Principle或簡寫為LKP):如果兩個類不彼此之間直接通信,那么這兩個類就不應(yīng)當發(fā)生

7、直接的相互作用。如果其中一個類需要調(diào)用另一個類的某個方法的話,可以通過第三者轉(zhuǎn)發(fā)這個調(diào)用。 迪米特法則首先強調(diào)的前提是在類的結(jié)構(gòu)設(shè)計上,每一個類都應(yīng)當盡量降低成員的訪問權(quán)限。 迪米特法則其根本思想強調(diào)的是類之間的松耦合。類之間的耦合越弱,越利于復用,一個處于弱耦合的類被修改,不會對有關(guān)系的類造成波及。7. 合成/聚合復用原則(Composite/Aggregate Reuse Principle或CARP)合成/聚合復用原則(Composite/Aggregate Reuse Principle或CARP):經(jīng)常又叫做合成復用原則(Composite Reuse Principle或CRP),

8、就是在一個新的對象里面使用一些已有的對象,使之成為新對象的一部分;新對象通過向這些對象的委派達到復用已有功能的目的。我白了就是要盡量使用合成/聚合,盡量不要使用繼承。本文來自CSDN博客,轉(zhuǎn)載請標明出處:黑箱復用法則“封裝、繼承、多態(tài)”是面向?qū)ο缶幊痰娜筇匦??!懊利?、智慧、大方”是(我認為的)女人應(yīng)具有的三大優(yōu)點。然而我可以經(jīng)??洫勔粋€女人“最美麗,最智慧,最大方”;但我從來不敢自吹自己寫的程序“最封裝,最繼承,最多態(tài)”。因為“封裝、繼承、多態(tài)”之間屬于相形相克。相形者,指三者中缺少任意一個,則余下二個都將不存在;相克者,則是三者中任意一個如果被發(fā)揮或表現(xiàn)到極限,則余者同樣無法生存。正由于此

9、,可以說,在抽象意義上,任何時候我們進行的程序設(shè)計1,都是力圖在針對當前的問題,調(diào)整出這三個特性的各自的最佳“實現(xiàn)度”。這也是所有程序員在不斷培養(yǎng),苦苦追求的設(shè)計能力。什么叫高手、老手?什么叫新手、生手?雖然我懂得“鹽是咸的,味精是甜的,姜是辛”,但我始終做不出一手好湯。雖然我也明白“油門、剎車、方向盤”的作用,但當舒馬赫在F1賽道上藝術(shù)地操作這三者時,我還在某個坡路上流汗:“又要剎車又要加油門為難人??!”是的,編程的難點與技藝正在于此。好的程序設(shè)計得讓人幾乎要歸之為“藝術(shù)”;而糟糕的設(shè)計,就像一個蹩腳的廚子走了,留下一桌惡心的菜,你卻不得不去咀嚼它,消化它,其間之苦,真非言語所能表達的。很不

10、幸,我就是這樣一個蹩腳的廚子,說起來“飯菜”也做了10年矣,但依然無法用“封裝、繼承、多態(tài)”做“面向?qū)ο蟆边@一道菜。我這學習編程的10載,倒幾近完整地見證了中國軟件開發(fā)行業(yè)發(fā)展的全過程。我也看到了很多程序員都和我一樣,在不斷的摸索、碰壁中緩慢地成長。好在,編程界的泰斗終于感到于心不忍了,開始提供編程界的“菜譜”。這就是如今火熱之至的“設(shè)計模式”2。當然,正如萬有引力一直存在著,而不是等到牛頓被蘋果砸了以后才出現(xiàn)?!霸O(shè)計模式”其實一直存在于優(yōu)秀程序員的設(shè)計里。不過是沒有形諸于文字,而是表現(xiàn)在代碼中。當然,也遠遠沒有泰斗們所歸結(jié)出來的模式那樣具備抽象性、概括性和通用性。說到設(shè)計模式,我只是想進一步

11、證明,一個程序員面對不同問題,其駕馭面向?qū)ο笕筇匦缘哪芰Φ闹匾?。這篇小文不準備講那23個經(jīng)典模式。我想以最基本的“白箱、黑箱復用”為例,開始我的論題。首先,不要看到“黑白箱”就想到測試?!袄^承、聚合”分別二者的原意,在不同的編程語言里可能有不同的術(shù)語,這里我們用最直觀的“白箱”,“黑箱”來表述。所引用的代碼,來自于早些日子我在“非程序員”(一個國內(nèi)專講UML的網(wǎng)站)的BBS的發(fā)言。那時有一位可能比我還菜的家伙在上面質(zhì)疑復用為何要分“白箱”“黑箱”,我一時技癢,上去口水了一番。下面我會通過一個有關(guān)“項目”的故事,將我當時用來論證的代碼,串成一次代碼設(shè)計的演進。先得說說“繼承”。C+ 提供了三

12、種三繼承“private, protected, public”??紤]到JAVA和C# 均只能支持最通用的 public 繼承,我們這里就僅以此為繼承的標準。所謂的public 繼承,字面意思是“公開繼承”。要個比方就是除了“老子”聲明要帶到陰間的財產(chǎn),其它的,它繼承者,都可以獲得,使用。(這是一個蹩腳的比喻,但我想你會承認它確實表達了公開繼承和其它繼承的不同L )?!肮_繼承”就是一種最常見的白箱復用的設(shè)計。它表示:“B復用A的功能,并且B可以了解A的內(nèi)部細節(jié)”。接下來我們講“黑箱復用”??梢酝茰y,它表示:“B復用A的功能,但B無法看到A的內(nèi)部細節(jié)”。這在像C#或JAVA這樣不支持私有或保護

13、繼承,也不支持多重繼承的語言,是一種極其常見的設(shè)計?!昂谙鋸陀谩钡膶崿F(xiàn)方法是:如果B類想復用A類的功能,不是從A類派生,而是將A類的對象,聲明成為B類的成員數(shù)據(jù)。嗯,是該來舉一個“實際項目”了。通過演示這個“項目”的實作,我想,就算你是外行人,你也應(yīng)能了解一點:事實上程序員最后用指頭敲寫代碼,其實那不算是編程,真的編程,在于之前他的大腦必須做的分析與設(shè)計(這句話一會兒我會繼續(xù)重復)。(聲明一下,以下故事純屬虛構(gòu))10年的編程生涯,嗯,我的家里有5臺電腦了。書房和臥室各有一間,但因為搬家所以常常換,老婆也有一個(不允許再多,也不允許換)。前三年又有了一個女兒,由于出生“腦香門第”,所以最近小家伙

14、也開始用上我的電腦。這些算是項目的背景和資源。項目的初始需求是這樣:結(jié)婚后每天晚上我都在書房時和電腦打交道到很晚。于是我老婆認為應(yīng)該把那臺筆記本搬到臥室,并責令我寫個程序,可以實現(xiàn)她在臥室通過電腦向我發(fā)號施令。這樣就有了本項目的產(chǎn)生。作為“客戶”,老婆當然希望她可以發(fā)各種各樣的命令;而作為該項目的產(chǎn)品經(jīng)理、技術(shù)架構(gòu)師,開發(fā)負責人,代碼撰寫者及測試師于一身的我,當然明白正確地引導客戶的需求是一個項目是否成功的最重要前提之一,同時也是對客戶負責的表現(xiàn)。我向她解釋了一個無所不包的軟件,首先將讓用戶界面變得繁雜無比,用戶極易操作失誤,而失去耐心;其次是眾多功能之間將互相牽制,導致表面上得到一個無所不包

15、的軟件,實際功能卻強項不強,弱項更弱等等最后我也委婉地提到了它對開發(fā)周期的可能的影響,以及在開發(fā)和后期維護費用上恐怕會出現(xiàn)幾何級的增長最后約定是只實現(xiàn)最為常見兩條命令的發(fā)送:a)“老公限N分種內(nèi)來睡覺,否則門將反鎖?!?;b)“腳已洗好,請來端盆?!庇辛司唧w的需求描述,這下顯得清楚多了。當然,老婆也不吃素的。在具體功能之外,也提出一些速度,性能的要求(這樣就可以杜絕我在限定時間內(nèi)無反應(yīng),會推托是軟件傳送命令太慢等后路),最重要一點也提到該系統(tǒng)應(yīng)具備一定的擴展性,以備今后增加新的命令的要求等等需求之后是概要設(shè)計,首先我確定通過局域網(wǎng),采用SOCKET來實現(xiàn)傳輸,而不是通過串口并口紅外線或藍牙。無論

16、是硬件還是軟件,這方面的資源均充備,這算是對開發(fā)資源做了認真詳實的調(diào)研并確定。然后我把數(shù)據(jù)流圖畫到了概要設(shè)計。在概要設(shè)計內(nèi),我也決定了將有采用.Net + C# 來進行開發(fā),當然,也提到了采用Win32+ C或C+或JAVA 的可能性。最后我也在概要設(shè)計里提出,由于該系統(tǒng)的簡小,在速度,性能,及擴展性并無太多要求,所以應(yīng)將設(shè)計的天平側(cè)向于“易用性”(以博取老婆歡心)。界面上的東西,及第二條命令數(shù)據(jù)的流程,均略。之后開始詳細設(shè)計,秉承概要設(shè)計的思想,我覺得將兩條命令的發(fā)送分別提供。那么要不要采用“多態(tài)”?即是否將發(fā)送兩條命令的發(fā)送動作取同一命名?考慮到以后可能會有新的命令擴展,這里采用多態(tài)會帶來

17、麻煩。所以我在這一步詳細設(shè)計里,放棄多態(tài)特性之一。很顯然,我對“擴展性”雖然沒有完全忽略,甚至是在概要和詳細設(shè)計里都可見“擴展性”的影響,但問題我缺少對“擴展”與“易用”做深入的,更具體的考慮。所以下一步的錯誤的根本,已經(jīng)埋下來。下面我開始提供設(shè)計的偽代碼。假設(shè)C# 提供基類Socket,用于在網(wǎng)絡(luò)發(fā)送數(shù)據(jù)。我沒有標出函數(shù)參數(shù) data 的數(shù)據(jù)類型。但顯然,作為該類的設(shè)計者,他并不知道你要發(fā)送什么樣的數(shù)據(jù)(老婆的命令?老板的命令?)所以這個Send () 可以發(fā)送的 data 肯定是無具體含義的。我們可稱為無格式的數(shù)據(jù)。而我們要發(fā)送的兩個有著具體意義的命令。根據(jù)前面設(shè)計。我們需要為這兩個類分別

18、提供發(fā)送函數(shù)。當然,這兩個發(fā)送具體命令的函數(shù),最終肯定是要調(diào)用上述系統(tǒng)提供的Send()命令來完成實際發(fā)送操作。讓我們來繼承它:是的,我派生了一個新類:“臥室的Socket”。這一命名表征了我心里其實很清楚,我要設(shè)計一個僅供臥室那端的人使用的Socket。而我對兩個具體的命令,提供了名字直觀的兩個函數(shù),這也充分體現(xiàn)了我正在按概要設(shè)計的要求進行詳細設(shè)計:請看,通過我對原來的Socket 類的派生,以及我對它的Send()動作的擴展,就在原來抽象的,無特別應(yīng)用方向的類的基礎(chǔ)上,得到一個新類,它有具體應(yīng)用方向,也有具體意義的動作。這比起拿起Socket就直接使用的人(這類人往往是C的高手);或者比起

19、為了“多態(tài)”而“多態(tài)”,從而把新加的兩個函數(shù)也命名為Send()的人(這往往是剛接觸C+才幾天的人),我的這個設(shè)計,確實顯得很正確。然而,事實上,這個設(shè)計在面向?qū)ο缶幊痰氖澜缋?,仍然是一個拙劣的設(shè)計。在面向?qū)ο缶幊填I(lǐng)域里有經(jīng)驗的程序員,我想已經(jīng)看出其中的欠妥之處。假設(shè)這個項目付諸實施了。當老婆的人倒也沒有提出什么擴展。光陰荏苒,結(jié)婚三年過去了,我們有了一個孩子;然后又是三年過去了,我們的孩子也開始會在電腦上施展她的天才。對這個軟件提出了她看法:“爸爸,應(yīng)該增加一個給我送牛奶的命令”。擴展需求終于出現(xiàn)了。然而,6年過去了,我對這個軟件的記憶是零。沒有看設(shè)計文檔,我就開始看代碼。然后我看到一個類:

20、BedroomSocket。我開始使用它,然后我看到它有三個有關(guān)發(fā)送的方法:bool Send();bool SendSackCommand();bool SendFootBathCommand();作為一個使用者,我并不想去花時間了解BedroomSocket的具體細節(jié),所以我并不知道其中那個Send() 其實是來自Socket這個基類(在實際大型項目開發(fā)中,比如大型ERP,專門寫上層業(yè)務(wù)邏輯的程序員,甚至是沒有權(quán)限可以看到他所使用的類的設(shè)計文檔,更看不到源代碼)。我錯誤地認為當初設(shè)計BedromSocket時,可能是為了易于對付一些新加的命令,所以提供了一個通用的Send()方法。就這樣,

21、縱然有100個項目經(jīng)理,也無法在第一時間內(nèi)阻止我義無反顧地通過BedroomSocket的實例來調(diào)用Send(),我會發(fā)現(xiàn)這個Send()實在太好用了,什么格式的數(shù)據(jù)都可以發(fā)送。也就這樣,一個項目原來的設(shè)計傾向開始出現(xiàn)偏差。如果這種情況在多人之間出現(xiàn)多次,那么一個項目的設(shè)計風格與模式,就將被每個人的理解而肢解成五花八門。不僅僅是在人的方面:理解,改錯,擴展等方面會增加難度,而且對于代碼本身,也必然由于模塊之間接合困難,而需要增加很多附加代碼,最終是程序運行效率低下。你可以怪罪后來者(在這個例子里仍然是“我”),不去深入學習需求,概要,設(shè)計文檔。但正如我前面所言,對一個大的項目,會按設(shè)計的層次分

22、成多個子項目; 要每一個人都去學習每一個項目的詳細設(shè)計文檔,并且最好是從需求開始看起,這是不可能的。再考慮那些中間件的實現(xiàn),通常都凝聚了一個軟企的核心技術(shù)這種情況下,分配在實現(xiàn)業(yè)務(wù)邏輯的程序員,沒有權(quán)限去學習中間件的具體設(shè)計思路。大家看到的,永遠只是對方的接口。類似于我看到了Bedroom接口透露出來的三個方法,但我不知道這些方法的實現(xiàn)背景。針對這個例子中碰上的問題。我們可以將“白箱復用”(這里是繼承),改為“黑箱復用”。在這次設(shè)計中,Socket 的對象成為類 BedroomCommand的一個成員。類BedroomCommand不再是通過“繼承”來獲得網(wǎng)絡(luò)發(fā)送的能力。而是通過“擁有”一個S

23、ocket對象來獲得該對象所有公開的能力。由于Socket 的對象sender 在BedroomCommand 里被聲明為私有(private,,或者也可以是保護protected),所以,有關(guān)Socket網(wǎng)絡(luò)發(fā)送的能力,僅有BedroomCommand 的設(shè)計者可以直接獲取和使用。這就是“黑箱復用”的一種常見方法。BedroomCommand 的使用者不再需要面對 Send() 。它所能看見和用到的接口,是BedroomCommand 提供三個意義明確的發(fā)送方法: public bool SendSackCommand (); public bool SendFootBathCommand

24、(); public bool SendMilkCommand();這樣,我們就解決前面的問題。我們實現(xiàn)了一個類,它提供了它應(yīng)有的功能,同時杜絕提供它不該有的功能。這正是一個良好的設(shè)計的基本標準。這么看來,是不是黑箱復用總是白箱復用來得正確?答案當然不是如此,下面我們繼續(xù)給這個設(shè)計制造問題想要給“設(shè)計”制造問題,最好的辦法就是修改“需求”了。我們假設(shè)原來的 Socket 類在除了提供一個公開對外的Send()方法以外,還提供了一個保護的SetOptions()方法。該方法用于對網(wǎng)絡(luò)發(fā)送做一些參數(shù)調(diào)整,以便可以定制出更符合具體要求的網(wǎng)絡(luò)發(fā)送能力。class Socketpublic Send (

25、data); /發(fā)送數(shù)據(jù) protected SetOptions(); /定制網(wǎng)絡(luò)條件 ;Socket 的設(shè)計者,認為SetOptions這一能力是不能直接對外公開,所以SetOptions被設(shè)計為“保護(protected)”。這就使得:除非是Socket本身或它的繼承它的類,否則就無法使用到SetOptions。我們前面講的“白箱復用”,正是繼承。這就給我們出現(xiàn)一個兩難:如果使用“白箱復用”,那么我們可以獲得我們想要的SetOptions,但同時我們卻不得不公開了我們不想公開的Send。如果使用“黑箱復用”,那么們可以不公開Send。但卻無法獲得SetOptions的能力。由此產(chǎn)生了“復

26、合復用”。(一般來說,SetOptions() 在 Socket 里不會被設(shè)置成 virtual,所以在C# 里,我們加上 new 指示符,而在C+,最直接的方法是另取一個名字,比如叫 SetMyOptions(),如此可以避免關(guān)于編譯器說我們覆蓋了基類同名函數(shù)的小問題。如果SetOptions是virtual類,則不存在該問題。另外,在C+里, base.SetOptions(),應(yīng)寫成: Socket:SetOptions())問題得以完美解決。Socket提供的超強能力,只有BedroomCommand的設(shè)計者能獲得,使用。并且通過BedroomCommand的設(shè)計者來決定要對外公開哪些

27、能力。任何一個后來的程序員,無論他是老手還是新手,都不會在使用BedroomCommand上出現(xiàn)偏差。就算是我在下一個6年之后,我也能正確地使用BedroomCommand。這樣的一個設(shè)計,針對當前問題,做到既有“粒度”又有“彈性”。由此引申出幾個話題。第一,關(guān)于需求分析、概要設(shè)計、詳細設(shè)計的劃分。概要設(shè)計更多地是在將需求模塊轉(zhuǎn)換為設(shè)計模塊。它從總體上把握了技術(shù)設(shè)計的可行性。著重表達各個設(shè)計模塊之間的靜態(tài)及動態(tài)關(guān)系,并由此確定各設(shè)計模塊之間接口規(guī)劃。一般地說,概要設(shè)計并不需求每個寫代碼的人都參加直接參加設(shè)計。它要求項目技術(shù)負責人了解技術(shù)實現(xiàn)上可行性,總體難度;它也要求技術(shù)負責人具備把握整個設(shè)計

28、的風格、傾向、取舍;但它并不要求技術(shù)負責懂得每一個模塊的具體實現(xiàn)。本例中,如果想在概要設(shè)計中就明確好BedroomCommand是如何實現(xiàn),這是不現(xiàn)實的。一個概要設(shè)計的負責人如果此時就開始關(guān)心每個類是如何實現(xiàn)(繼承?聚合?),那將使他陷入細節(jié)實現(xiàn)的泥潭。從而根本無法在整體把握設(shè)計。在概要設(shè)計的形成過程中,項目技術(shù)負責人必須基本得出項目開發(fā)的分工安排。最后由每個程序員以“概要設(shè)計”為綱領(lǐng),分頭研究+相互研討,逐步得出各自負責模塊的詳細設(shè)計。詳細設(shè)計也負有對概要設(shè)計反證的功能,當發(fā)現(xiàn)無法得出概要設(shè)計的實現(xiàn)時,應(yīng)提交討論,修改概要設(shè)計。當然,不同的項目情況,對需求,概要,詳細工作劃分,也會有所不同。

29、有些行業(yè)用戶,比如稅務(wù),銀行,由于IT實施較早,本身具備有相當?shù)能浖_發(fā)能力。他們往往可以直接嚴格的,詳細的需求,到軟企的手里,不僅僅需求文檔有了,連概要設(shè)計也出了一半。而更多的用戶,比如企業(yè)用戶,在需求上往往只有一句話:“我要一套超強的ERP,解決我所有問題_”。顯然只能先行挖掘用戶的“需求”了。第二,關(guān)于程序員的設(shè)計分工設(shè)計模塊不是以“需求功能模塊”來劃分。大家的分工,一般也不應(yīng)直接按功能模塊分配。因為這樣將迫使每個程序員都成為“七項全能”。這個問題的另一面,就是我個人認為,公司在招聘技術(shù)人才上,幾乎沒有任何“定向測試”,也沒能得出任何量化的結(jié)果。而設(shè)計分工要求我們了解每個程序員特長,從而

30、合理地定位。我個人非常傾向于以“界面(UI)、業(yè)務(wù)邏輯,中間件(包括定制控件),底層模塊”為縱線先進行層面上的設(shè)計任務(wù)安排。然后如有需要,再在各個層面進行橫向劃分。代碼復用是絕大多數(shù)程序員所期望的,也是OO的目標之一??偨Y(jié)我多年的編碼經(jīng)驗,為了使代碼能夠最大程度上復用,應(yīng)該特別注意以下幾個方面。1、 對接口編程 "對接口編程"是面向?qū)ο笤O(shè)計(OOD)的第一個基本原則。它的含義是:使用接口和同類型的組件通訊,即,對于所有完成相同功能的組件,應(yīng)該抽象出一個接口,它們都實現(xiàn)該接口。具體到JAVA中,可以是接口(interface),或者是抽象類(abstract class),所

31、有完成相同功能的組件都實現(xiàn)該接口,或者從該抽象類繼承。我們的客戶代碼只應(yīng)該和該接口通訊,這樣,當我們需要用其它組件完成任務(wù)時,只需要替換該接口的實現(xiàn),而我們代碼的其它部分不需要改變!當現(xiàn)有的組件不能滿足要求時,我們可以創(chuàng)建新的組件,實現(xiàn)該接口,或者,直接對現(xiàn)有的組件進行擴展,由子類去完成擴展的功能。2、 優(yōu)先使用對象組合,而不是類繼承 "優(yōu)先使用對象組合,而不是類繼承"是面向?qū)ο笤O(shè)計的第二個原則。并不是說繼承不重要,而是因為每個學習OOP的人都知道OO的基本特性之一就是繼承,以至于繼承已經(jīng)被濫用了,而對象組合技術(shù)往往被忽視了。下面分析繼承和組合的優(yōu)缺點:類繼承允許你根據(jù)其他

32、類的實現(xiàn)來定義一個類的實現(xiàn)。這種通過生成子類的復用通常被稱為白箱復用(white-box reuse)。術(shù)語"白箱"是相對可視性而言:在繼承方式中,父類的內(nèi)部細節(jié)對子類可見。對象組合是類繼承之外的另一種復用選擇。新的更復雜的功能可以通過組合對象來獲得。對象組合要求對象具有良好定義的接口。這種復用風格被稱為黑箱復用(black-box reuse),因為被組合的對象的內(nèi)部細節(jié)是不可見的。對象只以"黑箱"的形式出現(xiàn)。繼承和組合各有優(yōu)缺點。類繼承是在編譯時刻靜態(tài)定義的,且可直接使用,類繼承可以較方便地改變父類的實現(xiàn)。但是類繼承也有一些不足之處。首先,因為繼承在

33、編譯時刻就定義了,所以無法在運行時刻改變從父類繼承的實現(xiàn)。更糟的是,父類通常至少定義了子類的部分行為,父類的任何改變都可能影響子類的行為。如果繼承下來的實現(xiàn)不適合解決新的問題,則父類必須重寫或被其他更適合的類替換。這種依賴關(guān)系限制了靈活性并最終限制了復用性。對象組合是通過獲得對其他對象的引用而在運行時刻動態(tài)定義的。由于組合要求對象具有良好定義的接口,而且,對象只能通過接口訪問,所以我們并不破壞封裝性;只要類型一致,運行時刻還可以用一個對象來替代另一個對象;更進一步,因為對象的實現(xiàn)是基于接口寫的,所以實現(xiàn)上存在較少的依賴關(guān)系。優(yōu)先使用對象組合有助于你保持每個類被封裝,并且只集中完成單個任務(wù)。這樣

34、類和類繼承層次會保持較小規(guī)模,并且不太可能增長為不可控制的龐然大物(這正是濫用繼承的后果)。另一方面,基于對象組合的設(shè)計會有更多的對象(但只有較少的類),且系統(tǒng)的行為將依賴于對象間的關(guān)系而不是被定義在某個類中。注意:理想情況下,我們不用為獲得復用而去創(chuàng)建新的組件,只需要使用對象組合技術(shù),通過組裝已有的組件就能獲得需要的功能。但是事實很少如此,因為可用的組件集合并不豐富。使用繼承的復用使得創(chuàng)建新的組件要比組裝已有的組件來得容易。這樣,繼承和對象組合常一起使用。然而,正如前面所說,千萬不要濫用繼承而忽視了對象組合技術(shù)。相關(guān)的設(shè)計模式有:Bridge、Composite、Decorator、Obse

35、rver、Strategy等。下面的例子演示了這個規(guī)則,它的前提是:我們對同一個數(shù)據(jù)結(jié)構(gòu),需要以任意的格式輸出。第一個例子,我們使用基于繼承的框架,可以看到,它很難維護和擴展。abstract class AbstractExampleDocument/ skip some code .public void output(Example structure)if( null != structure )this.format( structure );protected void format(Example structure);第二個例子,我們使用基于對象組合技術(shù)的框架,每個對象的任務(wù)都

36、清楚的分離開來,我們可以替換、擴展格式類,而不用考慮其它的任何事情。 class DefaultExampleDocument / skip some code .public void output(Example structure) ExampleFormatter formatter = (ExampleFormatter) manager.lookup(Roles.FORMATTER);if( null != structure ) formatter.format(structure);這里,用到了類似于"抽象工廠"的組件創(chuàng)建模式,它將組件的創(chuàng)建過程交給mana

37、ger來完成;ExampleFormatter是所有格式的抽象父類;3、 將可變的部分和不可變的部分分離 "將可變的部分和不可變的部分分離"是面向?qū)ο笤O(shè)計的第三個原則。如果使用繼承的復用技術(shù),我們可以在抽象基類中定義好不可變的部分,而由其子類去具體實現(xiàn)可變的部分,不可變的部分不需要重復定義,而且便于維護。如果使用對象組合的復用技術(shù),我們可以定義好不可變的部分,而可變的部分可以由不同的組件實現(xiàn),根據(jù)需要,在運行時動態(tài)配置。這樣,我們就有更多的時間關(guān)注可變的部分。對于對象組合技術(shù)而言,每個組件只完成相對較小的功能,相互之間耦合比較松散,復用率較高,通過組合,就能獲得新的功能。4

38、、 減少方法的長度 通常,我們的方法應(yīng)該只有盡量少的幾行,太長的方法會難以理解,而且,如果方法太長,則應(yīng)該重新設(shè)計。對此,可以總結(jié)為以下原則: 三十秒原則:如果另一個程序員無法在三十秒之內(nèi)了解你的函數(shù)做了什么(What),如何做(How)以及為什么要這樣做(Why),那就說明你的代碼是難以維護的,必須得到提高; 一屏原則:如果一個函數(shù)的代碼長度超過一個屏幕,那么或許這個函數(shù)太長了,應(yīng)該拆分成更小的子函數(shù); 一行代碼盡量簡短,并且保證一行代碼只做一件事那種看似技巧性的冗長代碼只會增加代碼維護的難度。5、 消除case / if語句 要盡量避免在代碼中出現(xiàn)判斷語句,來測試一個對象是否某個特定類的實例。通常,如果你需要這么做,那么,重新設(shè)計可能會有所幫助。我在工作中遇到這樣的一個問題:我們在使用JAVA做XML解析時,對每個標簽映射了一個JAVA類,采用SAX(簡單的XML接口API:Simple API for XML)模型。結(jié)果,代碼中反復出現(xiàn)了大量的判斷語句,來測試當前的標簽類型。為此,我們重新設(shè)計了DTD(文檔類型定義:Document Type Definition),為每個標簽增加了一個固定的屬性:classname,而且重新設(shè)計了每個標簽映射的JAVA類的接口,統(tǒng)一了每個對象的操作: addElement(Element aElement); /增加子元素addAt

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論