![面向?qū)ο笃叽笤瓌t——肖文斌_第1頁](http://file3.renrendoc.com/fileroot_temp3/2022-2/12/60cf356c-9b36-4abb-bc48-4587197351b4/60cf356c-9b36-4abb-bc48-4587197351b41.gif)
![面向?qū)ο笃叽笤瓌t——肖文斌_第2頁](http://file3.renrendoc.com/fileroot_temp3/2022-2/12/60cf356c-9b36-4abb-bc48-4587197351b4/60cf356c-9b36-4abb-bc48-4587197351b42.gif)
![面向?qū)ο笃叽笤瓌t——肖文斌_第3頁](http://file3.renrendoc.com/fileroot_temp3/2022-2/12/60cf356c-9b36-4abb-bc48-4587197351b4/60cf356c-9b36-4abb-bc48-4587197351b43.gif)
![面向?qū)ο笃叽笤瓌t——肖文斌_第4頁](http://file3.renrendoc.com/fileroot_temp3/2022-2/12/60cf356c-9b36-4abb-bc48-4587197351b4/60cf356c-9b36-4abb-bc48-4587197351b44.gif)
![面向?qū)ο笃叽笤瓌t——肖文斌_第5頁](http://file3.renrendoc.com/fileroot_temp3/2022-2/12/60cf356c-9b36-4abb-bc48-4587197351b4/60cf356c-9b36-4abb-bc48-4587197351b45.gif)
版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、1. 單一職責(zé)原則(SRP)單一職責(zé)原則(SRP),就一個(gè)類而言,應(yīng)該僅有一個(gè)引起它變化的原因。也就是說,不要把變化原因各不相同的職責(zé)放在一起,因?yàn)椴煌淖兓瘯?huì)影響到不相干的職責(zé)。再通俗一點(diǎn)地說就是,不該你管的事情你不要管,管好自己的事情就可以了,多管閑事害了自己也害了別人。在軟件設(shè)計(jì)中,如果一個(gè)類承擔(dān)的職責(zé)過多,就等于吧這些職責(zé)耦合在一起,而一個(gè)職責(zé)的變化可能會(huì)削弱和抑制這個(gè)類完成其他職責(zé)的能力。這耦合會(huì)導(dǎo)致脆弱的設(shè)計(jì),當(dāng)變化發(fā)生時(shí),設(shè)計(jì)會(huì)遭受到意想不到的破壞。軟件設(shè)計(jì)真正要做的許多內(nèi)容,就是發(fā)現(xiàn)職責(zé)并把那些職責(zé)相互分離。如果多于一個(gè)的動(dòng)機(jī)去改變一個(gè)類,那么這個(gè)類就具有多余一個(gè)的職責(zé),就應(yīng)該
2、要考慮類的職責(zé)分離。2. 開放-封閉原則(The Open-Closed Principle 簡(jiǎn)稱OCP)開放-封閉原則,或叫開-閉原則,是說軟件實(shí)體(類、模塊、函數(shù)等)應(yīng)該是可以擴(kuò)展的,但是不可修改。不修改的意思就是是“你可以隨便增加新的類,但是不要修改原來的類”。從這個(gè)角度去理解就好多了,其實(shí)這里還是一個(gè)隔離變化的問題。這個(gè)原則的兩個(gè)特征:一個(gè)是對(duì)于擴(kuò)展是開放的;另一個(gè)是對(duì)于更改是封閉的。我們?cè)谠O(shè)計(jì)開發(fā)任何系統(tǒng)時(shí),都不可能指望系統(tǒng)一開始就需求確定,就不再變化(要這樣就太幸福了,哈哈),這是不現(xiàn)實(shí)的也是不科學(xué)的想法。既然需求是有一定變化的,那么如何在面對(duì)需求變化時(shí),設(shè)計(jì)的程序可以相對(duì)容易的修
3、改,不至于說,新需求一來,就要把整個(gè)程序推倒重來(這樣會(huì)讓程序員瘋了不可,哈哈,你不想瘋吧)。怎樣的設(shè)計(jì)才能面對(duì)需求的改變卻可以保持相對(duì)穩(wěn)定,從而使得系統(tǒng)可以在第一個(gè)版本以后不斷推出的新版本呢?開放-封閉原則就是我們的答案。在程序設(shè)計(jì)時(shí),我們要時(shí)刻考慮盡量把類設(shè)計(jì)的足夠好,寫好了就不要去修改,如果有新的需求來了,我們?cè)黾右恍╊悂硗瓿尚碌男枨?,原來的代碼能不動(dòng)就不動(dòng)。絕對(duì)的對(duì)修改關(guān)閉是不可能的,無論模塊是多么的封閉,都會(huì)存在一些無法對(duì)之封閉的變化,既然不能完全封閉,設(shè)計(jì)人員必須對(duì)他設(shè)計(jì)的模塊應(yīng)該對(duì)那種變化封閉做出抉擇、他必須事先猜測(cè)出最有可能發(fā)生變化的變化種類,然后構(gòu)建抽象來隔離那些變化。開放-
4、封閉原則是面向?qū)ο笤O(shè)計(jì)的核心所在。遵循這個(gè)原則可以帶來面向?qū)ο蠹夹g(shù)所生成的巨大好處,也就是可維護(hù)、可擴(kuò)展、可復(fù)用、靈活性好。開發(fā)人員應(yīng)該僅對(duì)程序中呈現(xiàn)出頻繁變化的部分都做出抽象,然后,對(duì)于應(yīng)用程序中的每部分都刻意定進(jìn)行抽象同樣不是一個(gè)好主意,拒絕不成熟的抽象和抽象本身一樣重要。3. 依賴倒轉(zhuǎn)原則(DIP Dependency Inversion Principle)依賴倒轉(zhuǎn)原則:抽象不應(yīng)該依賴于細(xì)節(jié)。細(xì)節(jié)應(yīng)該依賴于抽象;高層不應(yīng)該依賴于底層,兩者都應(yīng)該依賴于抽象。說白了就是要針對(duì)接口編程,不要針對(duì)實(shí)現(xiàn)編程。抽象的東西才是最穩(wěn)定的,也就是說,我們依賴的是它的穩(wěn)定。依賴倒轉(zhuǎn)其實(shí)可以說是面向?qū)ο笤O(shè)計(jì)
5、的標(biāo)志,用哪種語言來編寫程序不重要,如果編寫是考慮的都是如何針對(duì)抽象編程而不是針對(duì)細(xì)節(jié)編程,即程序中的所有的依賴關(guān)系都是終止與抽象類或者接口,那就是面向?qū)ο蟮脑O(shè)計(jì),反之就是過程化設(shè)計(jì)了。4. 里氏代換原則里氏代換原則(LSP):子類型必須能夠替換掉他的父類型。說白了就是一個(gè)軟件實(shí)體如果使用的是一個(gè)父類的話,那么一定適用于其子類,而他察覺不出父類對(duì)象和子類對(duì)象的區(qū)別,也就是說,在軟件里面,把父類都替換成他的子類,程序行為沒有變化。有了里氏替換原則,才是繼承復(fù)用成為可能,只有當(dāng)子類可以替換掉父類時(shí),軟件的功能不受到影響,父類才能真正被復(fù)用,而子類也能夠在父類的基礎(chǔ)上增加新的行為。有了里氏代換原則,
6、才能使開放-封閉原則成為可能,正是由于子類型的可替換性才使得父類型的模塊在無需修改的情況下擴(kuò)展。5. 接口隔離原則(ISP)接口隔離原則(ISP):不應(yīng)該強(qiáng)迫客戶依賴于它們不用的方法。接口屬于客戶,不屬于它所在的類層次結(jié)構(gòu)。這個(gè)說得很明白了,再通俗點(diǎn)說,不要強(qiáng)迫客戶使用它們不用的方法,如果強(qiáng)迫用戶使用它們不使用的方法,那么這些客戶就會(huì)面臨由于這些不使用的方法的改變所帶來的改變。6. 迪米特法則(LoD)迪米特法則(Law of Demeter或簡(jiǎn)寫LoD)又叫最少知識(shí)原則(Least Knowledge Principle或簡(jiǎn)寫為L(zhǎng)KP):如果兩個(gè)類不彼此之間直接通信,那么這兩個(gè)類就不應(yīng)當(dāng)發(fā)生
7、直接的相互作用。如果其中一個(gè)類需要調(diào)用另一個(gè)類的某個(gè)方法的話,可以通過第三者轉(zhuǎn)發(fā)這個(gè)調(diào)用。 迪米特法則首先強(qiáng)調(diào)的前提是在類的結(jié)構(gòu)設(shè)計(jì)上,每一個(gè)類都應(yīng)當(dāng)盡量降低成員的訪問權(quán)限。 迪米特法則其根本思想強(qiáng)調(diào)的是類之間的松耦合。類之間的耦合越弱,越利于復(fù)用,一個(gè)處于弱耦合的類被修改,不會(huì)對(duì)有關(guān)系的類造成波及。7. 合成/聚合復(fù)用原則(Composite/Aggregate Reuse Principle或CARP)合成/聚合復(fù)用原則(Composite/Aggregate Reuse Principle或CARP):經(jīng)常又叫做合成復(fù)用原則(Composite Reuse Principle或CRP),
8、就是在一個(gè)新的對(duì)象里面使用一些已有的對(duì)象,使之成為新對(duì)象的一部分;新對(duì)象通過向這些對(duì)象的委派達(dá)到復(fù)用已有功能的目的。我白了就是要盡量使用合成/聚合,盡量不要使用繼承。本文來自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:黑箱復(fù)用法則“封裝、繼承、多態(tài)”是面向?qū)ο缶幊痰娜筇匦??!懊利?、智慧、大方”是(我認(rèn)為的)女人應(yīng)具有的三大優(yōu)點(diǎn)。然而我可以經(jīng)??洫?jiǎng)一個(gè)女人“最美麗,最智慧,最大方”;但我從來不敢自吹自己寫的程序“最封裝,最繼承,最多態(tài)”。因?yàn)椤胺庋b、繼承、多態(tài)”之間屬于相形相克。相形者,指三者中缺少任意一個(gè),則余下二個(gè)都將不存在;相克者,則是三者中任意一個(gè)如果被發(fā)揮或表現(xiàn)到極限,則余者同樣無法生存。正由于此
9、,可以說,在抽象意義上,任何時(shí)候我們進(jìn)行的程序設(shè)計(jì)1,都是力圖在針對(duì)當(dāng)前的問題,調(diào)整出這三個(gè)特性的各自的最佳“實(shí)現(xiàn)度”。這也是所有程序員在不斷培養(yǎng),苦苦追求的設(shè)計(jì)能力。什么叫高手、老手?什么叫新手、生手?雖然我懂得“鹽是咸的,味精是甜的,姜是辛”,但我始終做不出一手好湯。雖然我也明白“油門、剎車、方向盤”的作用,但當(dāng)舒馬赫在F1賽道上藝術(shù)地操作這三者時(shí),我還在某個(gè)坡路上流汗:“又要?jiǎng)x車又要加油門為難人??!”是的,編程的難點(diǎn)與技藝正在于此。好的程序設(shè)計(jì)得讓人幾乎要?dú)w之為“藝術(shù)”;而糟糕的設(shè)計(jì),就像一個(gè)蹩腳的廚子走了,留下一桌惡心的菜,你卻不得不去咀嚼它,消化它,其間之苦,真非言語所能表達(dá)的。很不
10、幸,我就是這樣一個(gè)蹩腳的廚子,說起來“飯菜”也做了10年矣,但依然無法用“封裝、繼承、多態(tài)”做“面向?qū)ο蟆边@一道菜。我這學(xué)習(xí)編程的10載,倒幾近完整地見證了中國(guó)軟件開發(fā)行業(yè)發(fā)展的全過程。我也看到了很多程序員都和我一樣,在不斷的摸索、碰壁中緩慢地成長(zhǎng)。好在,編程界的泰斗終于感到于心不忍了,開始提供編程界的“菜譜”。這就是如今火熱之至的“設(shè)計(jì)模式”2。當(dāng)然,正如萬有引力一直存在著,而不是等到牛頓被蘋果砸了以后才出現(xiàn)。“設(shè)計(jì)模式”其實(shí)一直存在于優(yōu)秀程序員的設(shè)計(jì)里。不過是沒有形諸于文字,而是表現(xiàn)在代碼中。當(dāng)然,也遠(yuǎn)遠(yuǎn)沒有泰斗們所歸結(jié)出來的模式那樣具備抽象性、概括性和通用性。說到設(shè)計(jì)模式,我只是想進(jìn)一步
11、證明,一個(gè)程序員面對(duì)不同問題,其駕馭面向?qū)ο笕筇匦缘哪芰Φ闹匾?。這篇小文不準(zhǔn)備講那23個(gè)經(jīng)典模式。我想以最基本的“白箱、黑箱復(fù)用”為例,開始我的論題。首先,不要看到“黑白箱”就想到測(cè)試?!袄^承、聚合”分別二者的原意,在不同的編程語言里可能有不同的術(shù)語,這里我們用最直觀的“白箱”,“黑箱”來表述。所引用的代碼,來自于早些日子我在“非程序員”(一個(gè)國(guó)內(nèi)專講UML的網(wǎng)站)的BBS的發(fā)言。那時(shí)有一位可能比我還菜的家伙在上面質(zhì)疑復(fù)用為何要分“白箱”“黑箱”,我一時(shí)技癢,上去口水了一番。下面我會(huì)通過一個(gè)有關(guān)“項(xiàng)目”的故事,將我當(dāng)時(shí)用來論證的代碼,串成一次代碼設(shè)計(jì)的演進(jìn)。先得說說“繼承”。C+ 提供了三
12、種三繼承“private, protected, public”??紤]到JAVA和C# 均只能支持最通用的 public 繼承,我們這里就僅以此為繼承的標(biāo)準(zhǔn)。所謂的public 繼承,字面意思是“公開繼承”。要個(gè)比方就是除了“老子”聲明要帶到陰間的財(cái)產(chǎn),其它的,它繼承者,都可以獲得,使用。(這是一個(gè)蹩腳的比喻,但我想你會(huì)承認(rèn)它確實(shí)表達(dá)了公開繼承和其它繼承的不同L )。“公開繼承”就是一種最常見的白箱復(fù)用的設(shè)計(jì)。它表示:“B復(fù)用A的功能,并且B可以了解A的內(nèi)部細(xì)節(jié)”。接下來我們講“黑箱復(fù)用”??梢酝茰y(cè),它表示:“B復(fù)用A的功能,但B無法看到A的內(nèi)部細(xì)節(jié)”。這在像C#或JAVA這樣不支持私有或保護(hù)
13、繼承,也不支持多重繼承的語言,是一種極其常見的設(shè)計(jì)?!昂谙鋸?fù)用”的實(shí)現(xiàn)方法是:如果B類想復(fù)用A類的功能,不是從A類派生,而是將A類的對(duì)象,聲明成為B類的成員數(shù)據(jù)。嗯,是該來舉一個(gè)“實(shí)際項(xiàng)目”了。通過演示這個(gè)“項(xiàng)目”的實(shí)作,我想,就算你是外行人,你也應(yīng)能了解一點(diǎn):事實(shí)上程序員最后用指頭敲寫代碼,其實(shí)那不算是編程,真的編程,在于之前他的大腦必須做的分析與設(shè)計(jì)(這句話一會(huì)兒我會(huì)繼續(xù)重復(fù))。(聲明一下,以下故事純屬虛構(gòu))10年的編程生涯,嗯,我的家里有5臺(tái)電腦了。書房和臥室各有一間,但因?yàn)榘峒宜猿3Q,老婆也有一個(gè)(不允許再多,也不允許換)。前三年又有了一個(gè)女兒,由于出生“腦香門第”,所以最近小家伙
14、也開始用上我的電腦。這些算是項(xiàng)目的背景和資源。項(xiàng)目的初始需求是這樣:結(jié)婚后每天晚上我都在書房時(shí)和電腦打交道到很晚。于是我老婆認(rèn)為應(yīng)該把那臺(tái)筆記本搬到臥室,并責(zé)令我寫個(gè)程序,可以實(shí)現(xiàn)她在臥室通過電腦向我發(fā)號(hào)施令。這樣就有了本項(xiàng)目的產(chǎn)生。作為“客戶”,老婆當(dāng)然希望她可以發(fā)各種各樣的命令;而作為該項(xiàng)目的產(chǎn)品經(jīng)理、技術(shù)架構(gòu)師,開發(fā)負(fù)責(zé)人,代碼撰寫者及測(cè)試師于一身的我,當(dāng)然明白正確地引導(dǎo)客戶的需求是一個(gè)項(xiàng)目是否成功的最重要前提之一,同時(shí)也是對(duì)客戶負(fù)責(zé)的表現(xiàn)。我向她解釋了一個(gè)無所不包的軟件,首先將讓用戶界面變得繁雜無比,用戶極易操作失誤,而失去耐心;其次是眾多功能之間將互相牽制,導(dǎo)致表面上得到一個(gè)無所不包
15、的軟件,實(shí)際功能卻強(qiáng)項(xiàng)不強(qiáng),弱項(xiàng)更弱等等最后我也委婉地提到了它對(duì)開發(fā)周期的可能的影響,以及在開發(fā)和后期維護(hù)費(fèi)用上恐怕會(huì)出現(xiàn)幾何級(jí)的增長(zhǎng)最后約定是只實(shí)現(xiàn)最為常見兩條命令的發(fā)送:a)“老公限N分種內(nèi)來睡覺,否則門將反鎖?!?;b)“腳已洗好,請(qǐng)來端盆?!庇辛司唧w的需求描述,這下顯得清楚多了。當(dāng)然,老婆也不吃素的。在具體功能之外,也提出一些速度,性能的要求(這樣就可以杜絕我在限定時(shí)間內(nèi)無反應(yīng),會(huì)推托是軟件傳送命令太慢等后路),最重要一點(diǎn)也提到該系統(tǒng)應(yīng)具備一定的擴(kuò)展性,以備今后增加新的命令的要求等等需求之后是概要設(shè)計(jì),首先我確定通過局域網(wǎng),采用SOCKET來實(shí)現(xiàn)傳輸,而不是通過串口并口紅外線或藍(lán)牙。無論
16、是硬件還是軟件,這方面的資源均充備,這算是對(duì)開發(fā)資源做了認(rèn)真詳實(shí)的調(diào)研并確定。然后我把數(shù)據(jù)流圖畫到了概要設(shè)計(jì)。在概要設(shè)計(jì)內(nèi),我也決定了將有采用.Net + C# 來進(jìn)行開發(fā),當(dāng)然,也提到了采用Win32+ C或C+或JAVA 的可能性。最后我也在概要設(shè)計(jì)里提出,由于該系統(tǒng)的簡(jiǎn)小,在速度,性能,及擴(kuò)展性并無太多要求,所以應(yīng)將設(shè)計(jì)的天平側(cè)向于“易用性”(以博取老婆歡心)。界面上的東西,及第二條命令數(shù)據(jù)的流程,均略。之后開始詳細(xì)設(shè)計(jì),秉承概要設(shè)計(jì)的思想,我覺得將兩條命令的發(fā)送分別提供。那么要不要采用“多態(tài)”?即是否將發(fā)送兩條命令的發(fā)送動(dòng)作取同一命名?考慮到以后可能會(huì)有新的命令擴(kuò)展,這里采用多態(tài)會(huì)帶來
17、麻煩。所以我在這一步詳細(xì)設(shè)計(jì)里,放棄多態(tài)特性之一。很顯然,我對(duì)“擴(kuò)展性”雖然沒有完全忽略,甚至是在概要和詳細(xì)設(shè)計(jì)里都可見“擴(kuò)展性”的影響,但問題我缺少對(duì)“擴(kuò)展”與“易用”做深入的,更具體的考慮。所以下一步的錯(cuò)誤的根本,已經(jīng)埋下來。下面我開始提供設(shè)計(jì)的偽代碼。假設(shè)C# 提供基類Socket,用于在網(wǎng)絡(luò)發(fā)送數(shù)據(jù)。我沒有標(biāo)出函數(shù)參數(shù) data 的數(shù)據(jù)類型。但顯然,作為該類的設(shè)計(jì)者,他并不知道你要發(fā)送什么樣的數(shù)據(jù)(老婆的命令?老板的命令?)所以這個(gè)Send () 可以發(fā)送的 data 肯定是無具體含義的。我們可稱為無格式的數(shù)據(jù)。而我們要發(fā)送的兩個(gè)有著具體意義的命令。根據(jù)前面設(shè)計(jì)。我們需要為這兩個(gè)類分別
18、提供發(fā)送函數(shù)。當(dāng)然,這兩個(gè)發(fā)送具體命令的函數(shù),最終肯定是要調(diào)用上述系統(tǒng)提供的Send()命令來完成實(shí)際發(fā)送操作。讓我們來繼承它:是的,我派生了一個(gè)新類:“臥室的Socket”。這一命名表征了我心里其實(shí)很清楚,我要設(shè)計(jì)一個(gè)僅供臥室那端的人使用的Socket。而我對(duì)兩個(gè)具體的命令,提供了名字直觀的兩個(gè)函數(shù),這也充分體現(xiàn)了我正在按概要設(shè)計(jì)的要求進(jìn)行詳細(xì)設(shè)計(jì):請(qǐng)看,通過我對(duì)原來的Socket 類的派生,以及我對(duì)它的Send()動(dòng)作的擴(kuò)展,就在原來抽象的,無特別應(yīng)用方向的類的基礎(chǔ)上,得到一個(gè)新類,它有具體應(yīng)用方向,也有具體意義的動(dòng)作。這比起拿起Socket就直接使用的人(這類人往往是C的高手);或者比起
19、為了“多態(tài)”而“多態(tài)”,從而把新加的兩個(gè)函數(shù)也命名為Send()的人(這往往是剛接觸C+才幾天的人),我的這個(gè)設(shè)計(jì),確實(shí)顯得很正確。然而,事實(shí)上,這個(gè)設(shè)計(jì)在面向?qū)ο缶幊痰氖澜缋?,仍然是一個(gè)拙劣的設(shè)計(jì)。在面向?qū)ο缶幊填I(lǐng)域里有經(jīng)驗(yàn)的程序員,我想已經(jīng)看出其中的欠妥之處。假設(shè)這個(gè)項(xiàng)目付諸實(shí)施了。當(dāng)老婆的人倒也沒有提出什么擴(kuò)展。光陰荏苒,結(jié)婚三年過去了,我們有了一個(gè)孩子;然后又是三年過去了,我們的孩子也開始會(huì)在電腦上施展她的天才。對(duì)這個(gè)軟件提出了她看法:“爸爸,應(yīng)該增加一個(gè)給我送牛奶的命令”。擴(kuò)展需求終于出現(xiàn)了。然而,6年過去了,我對(duì)這個(gè)軟件的記憶是零。沒有看設(shè)計(jì)文檔,我就開始看代碼。然后我看到一個(gè)類:
20、BedroomSocket。我開始使用它,然后我看到它有三個(gè)有關(guān)發(fā)送的方法:bool Send();bool SendSackCommand();bool SendFootBathCommand();作為一個(gè)使用者,我并不想去花時(shí)間了解BedroomSocket的具體細(xì)節(jié),所以我并不知道其中那個(gè)Send() 其實(shí)是來自Socket這個(gè)基類(在實(shí)際大型項(xiàng)目開發(fā)中,比如大型ERP,專門寫上層業(yè)務(wù)邏輯的程序員,甚至是沒有權(quán)限可以看到他所使用的類的設(shè)計(jì)文檔,更看不到源代碼)。我錯(cuò)誤地認(rèn)為當(dāng)初設(shè)計(jì)BedromSocket時(shí),可能是為了易于對(duì)付一些新加的命令,所以提供了一個(gè)通用的Send()方法。就這樣,
21、縱然有100個(gè)項(xiàng)目經(jīng)理,也無法在第一時(shí)間內(nèi)阻止我義無反顧地通過BedroomSocket的實(shí)例來調(diào)用Send(),我會(huì)發(fā)現(xiàn)這個(gè)Send()實(shí)在太好用了,什么格式的數(shù)據(jù)都可以發(fā)送。也就這樣,一個(gè)項(xiàng)目原來的設(shè)計(jì)傾向開始出現(xiàn)偏差。如果這種情況在多人之間出現(xiàn)多次,那么一個(gè)項(xiàng)目的設(shè)計(jì)風(fēng)格與模式,就將被每個(gè)人的理解而肢解成五花八門。不僅僅是在人的方面:理解,改錯(cuò),擴(kuò)展等方面會(huì)增加難度,而且對(duì)于代碼本身,也必然由于模塊之間接合困難,而需要增加很多附加代碼,最終是程序運(yùn)行效率低下。你可以怪罪后來者(在這個(gè)例子里仍然是“我”),不去深入學(xué)習(xí)需求,概要,設(shè)計(jì)文檔。但正如我前面所言,對(duì)一個(gè)大的項(xiàng)目,會(huì)按設(shè)計(jì)的層次分
22、成多個(gè)子項(xiàng)目; 要每一個(gè)人都去學(xué)習(xí)每一個(gè)項(xiàng)目的詳細(xì)設(shè)計(jì)文檔,并且最好是從需求開始看起,這是不可能的。再考慮那些中間件的實(shí)現(xiàn),通常都凝聚了一個(gè)軟企的核心技術(shù)這種情況下,分配在實(shí)現(xiàn)業(yè)務(wù)邏輯的程序員,沒有權(quán)限去學(xué)習(xí)中間件的具體設(shè)計(jì)思路。大家看到的,永遠(yuǎn)只是對(duì)方的接口。類似于我看到了Bedroom接口透露出來的三個(gè)方法,但我不知道這些方法的實(shí)現(xiàn)背景。針對(duì)這個(gè)例子中碰上的問題。我們可以將“白箱復(fù)用”(這里是繼承),改為“黑箱復(fù)用”。在這次設(shè)計(jì)中,Socket 的對(duì)象成為類 BedroomCommand的一個(gè)成員。類BedroomCommand不再是通過“繼承”來獲得網(wǎng)絡(luò)發(fā)送的能力。而是通過“擁有”一個(gè)S
23、ocket對(duì)象來獲得該對(duì)象所有公開的能力。由于Socket 的對(duì)象sender 在BedroomCommand 里被聲明為私有(private,,或者也可以是保護(hù)protected),所以,有關(guān)Socket網(wǎng)絡(luò)發(fā)送的能力,僅有BedroomCommand 的設(shè)計(jì)者可以直接獲取和使用。這就是“黑箱復(fù)用”的一種常見方法。BedroomCommand 的使用者不再需要面對(duì) Send() 。它所能看見和用到的接口,是BedroomCommand 提供三個(gè)意義明確的發(fā)送方法: public bool SendSackCommand (); public bool SendFootBathCommand
24、(); public bool SendMilkCommand();這樣,我們就解決前面的問題。我們實(shí)現(xiàn)了一個(gè)類,它提供了它應(yīng)有的功能,同時(shí)杜絕提供它不該有的功能。這正是一個(gè)良好的設(shè)計(jì)的基本標(biāo)準(zhǔn)。這么看來,是不是黑箱復(fù)用總是白箱復(fù)用來得正確?答案當(dāng)然不是如此,下面我們繼續(xù)給這個(gè)設(shè)計(jì)制造問題想要給“設(shè)計(jì)”制造問題,最好的辦法就是修改“需求”了。我們假設(shè)原來的 Socket 類在除了提供一個(gè)公開對(duì)外的Send()方法以外,還提供了一個(gè)保護(hù)的SetOptions()方法。該方法用于對(duì)網(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è)計(jì)者,認(rèn)為SetOptions這一能力是不能直接對(duì)外公開,所以SetOptions被設(shè)計(jì)為“保護(hù)(protected)”。這就使得:除非是Socket本身或它的繼承它的類,否則就無法使用到SetOptions。我們前面講的“白箱復(fù)用”,正是繼承。這就給我們出現(xiàn)一個(gè)兩難:如果使用“白箱復(fù)用”,那么我們可以獲得我們想要的SetOptions,但同時(shí)我們卻不得不公開了我們不想公開的Send。如果使用“黑箱復(fù)用”,那么們可以不公開Send。但卻無法獲得SetOptions的能力。由此產(chǎn)生了“復(fù)
26、合復(fù)用”。(一般來說,SetOptions() 在 Socket 里不會(huì)被設(shè)置成 virtual,所以在C# 里,我們加上 new 指示符,而在C+,最直接的方法是另取一個(gè)名字,比如叫 SetMyOptions(),如此可以避免關(guān)于編譯器說我們覆蓋了基類同名函數(shù)的小問題。如果SetOptions是virtual類,則不存在該問題。另外,在C+里, base.SetOptions(),應(yīng)寫成: Socket:SetOptions())問題得以完美解決。Socket提供的超強(qiáng)能力,只有BedroomCommand的設(shè)計(jì)者能獲得,使用。并且通過BedroomCommand的設(shè)計(jì)者來決定要對(duì)外公開哪些
27、能力。任何一個(gè)后來的程序員,無論他是老手還是新手,都不會(huì)在使用BedroomCommand上出現(xiàn)偏差。就算是我在下一個(gè)6年之后,我也能正確地使用BedroomCommand。這樣的一個(gè)設(shè)計(jì),針對(duì)當(dāng)前問題,做到既有“粒度”又有“彈性”。由此引申出幾個(gè)話題。第一,關(guān)于需求分析、概要設(shè)計(jì)、詳細(xì)設(shè)計(jì)的劃分。概要設(shè)計(jì)更多地是在將需求模塊轉(zhuǎn)換為設(shè)計(jì)模塊。它從總體上把握了技術(shù)設(shè)計(jì)的可行性。著重表達(dá)各個(gè)設(shè)計(jì)模塊之間的靜態(tài)及動(dòng)態(tài)關(guān)系,并由此確定各設(shè)計(jì)模塊之間接口規(guī)劃。一般地說,概要設(shè)計(jì)并不需求每個(gè)寫代碼的人都參加直接參加設(shè)計(jì)。它要求項(xiàng)目技術(shù)負(fù)責(zé)人了解技術(shù)實(shí)現(xiàn)上可行性,總體難度;它也要求技術(shù)負(fù)責(zé)人具備把握整個(gè)設(shè)計(jì)
28、的風(fēng)格、傾向、取舍;但它并不要求技術(shù)負(fù)責(zé)懂得每一個(gè)模塊的具體實(shí)現(xiàn)。本例中,如果想在概要設(shè)計(jì)中就明確好BedroomCommand是如何實(shí)現(xiàn),這是不現(xiàn)實(shí)的。一個(gè)概要設(shè)計(jì)的負(fù)責(zé)人如果此時(shí)就開始關(guān)心每個(gè)類是如何實(shí)現(xiàn)(繼承?聚合?),那將使他陷入細(xì)節(jié)實(shí)現(xiàn)的泥潭。從而根本無法在整體把握設(shè)計(jì)。在概要設(shè)計(jì)的形成過程中,項(xiàng)目技術(shù)負(fù)責(zé)人必須基本得出項(xiàng)目開發(fā)的分工安排。最后由每個(gè)程序員以“概要設(shè)計(jì)”為綱領(lǐng),分頭研究+相互研討,逐步得出各自負(fù)責(zé)模塊的詳細(xì)設(shè)計(jì)。詳細(xì)設(shè)計(jì)也負(fù)有對(duì)概要設(shè)計(jì)反證的功能,當(dāng)發(fā)現(xiàn)無法得出概要設(shè)計(jì)的實(shí)現(xiàn)時(shí),應(yīng)提交討論,修改概要設(shè)計(jì)。當(dāng)然,不同的項(xiàng)目情況,對(duì)需求,概要,詳細(xì)工作劃分,也會(huì)有所不同。
29、有些行業(yè)用戶,比如稅務(wù),銀行,由于IT實(shí)施較早,本身具備有相當(dāng)?shù)能浖_發(fā)能力。他們往往可以直接嚴(yán)格的,詳細(xì)的需求,到軟企的手里,不僅僅需求文檔有了,連概要設(shè)計(jì)也出了一半。而更多的用戶,比如企業(yè)用戶,在需求上往往只有一句話:“我要一套超強(qiáng)的ERP,解決我所有問題_”。顯然只能先行挖掘用戶的“需求”了。第二,關(guān)于程序員的設(shè)計(jì)分工設(shè)計(jì)模塊不是以“需求功能模塊”來劃分。大家的分工,一般也不應(yīng)直接按功能模塊分配。因?yàn)檫@樣將迫使每個(gè)程序員都成為“七項(xiàng)全能”。這個(gè)問題的另一面,就是我個(gè)人認(rèn)為,公司在招聘技術(shù)人才上,幾乎沒有任何“定向測(cè)試”,也沒能得出任何量化的結(jié)果。而設(shè)計(jì)分工要求我們了解每個(gè)程序員特長(zhǎng),從而
30、合理地定位。我個(gè)人非常傾向于以“界面(UI)、業(yè)務(wù)邏輯,中間件(包括定制控件),底層模塊”為縱線先進(jìn)行層面上的設(shè)計(jì)任務(wù)安排。然后如有需要,再在各個(gè)層面進(jìn)行橫向劃分。代碼復(fù)用是絕大多數(shù)程序員所期望的,也是OO的目標(biāo)之一??偨Y(jié)我多年的編碼經(jīng)驗(yàn),為了使代碼能夠最大程度上復(fù)用,應(yīng)該特別注意以下幾個(gè)方面。1、 對(duì)接口編程 "對(duì)接口編程"是面向?qū)ο笤O(shè)計(jì)(OOD)的第一個(gè)基本原則。它的含義是:使用接口和同類型的組件通訊,即,對(duì)于所有完成相同功能的組件,應(yīng)該抽象出一個(gè)接口,它們都實(shí)現(xiàn)該接口。具體到JAVA中,可以是接口(interface),或者是抽象類(abstract class),所
31、有完成相同功能的組件都實(shí)現(xiàn)該接口,或者從該抽象類繼承。我們的客戶代碼只應(yīng)該和該接口通訊,這樣,當(dāng)我們需要用其它組件完成任務(wù)時(shí),只需要替換該接口的實(shí)現(xiàn),而我們代碼的其它部分不需要改變!當(dāng)現(xiàn)有的組件不能滿足要求時(shí),我們可以創(chuàng)建新的組件,實(shí)現(xiàn)該接口,或者,直接對(duì)現(xiàn)有的組件進(jìn)行擴(kuò)展,由子類去完成擴(kuò)展的功能。2、 優(yōu)先使用對(duì)象組合,而不是類繼承 "優(yōu)先使用對(duì)象組合,而不是類繼承"是面向?qū)ο笤O(shè)計(jì)的第二個(gè)原則。并不是說繼承不重要,而是因?yàn)槊總€(gè)學(xué)習(xí)OOP的人都知道OO的基本特性之一就是繼承,以至于繼承已經(jīng)被濫用了,而對(duì)象組合技術(shù)往往被忽視了。下面分析繼承和組合的優(yōu)缺點(diǎn):類繼承允許你根據(jù)其他
32、類的實(shí)現(xiàn)來定義一個(gè)類的實(shí)現(xiàn)。這種通過生成子類的復(fù)用通常被稱為白箱復(fù)用(white-box reuse)。術(shù)語"白箱"是相對(duì)可視性而言:在繼承方式中,父類的內(nèi)部細(xì)節(jié)對(duì)子類可見。對(duì)象組合是類繼承之外的另一種復(fù)用選擇。新的更復(fù)雜的功能可以通過組合對(duì)象來獲得。對(duì)象組合要求對(duì)象具有良好定義的接口。這種復(fù)用風(fēng)格被稱為黑箱復(fù)用(black-box reuse),因?yàn)楸唤M合的對(duì)象的內(nèi)部細(xì)節(jié)是不可見的。對(duì)象只以"黑箱"的形式出現(xiàn)。繼承和組合各有優(yōu)缺點(diǎn)。類繼承是在編譯時(shí)刻靜態(tài)定義的,且可直接使用,類繼承可以較方便地改變父類的實(shí)現(xiàn)。但是類繼承也有一些不足之處。首先,因?yàn)槔^承在
33、編譯時(shí)刻就定義了,所以無法在運(yùn)行時(shí)刻改變從父類繼承的實(shí)現(xiàn)。更糟的是,父類通常至少定義了子類的部分行為,父類的任何改變都可能影響子類的行為。如果繼承下來的實(shí)現(xiàn)不適合解決新的問題,則父類必須重寫或被其他更適合的類替換。這種依賴關(guān)系限制了靈活性并最終限制了復(fù)用性。對(duì)象組合是通過獲得對(duì)其他對(duì)象的引用而在運(yùn)行時(shí)刻動(dòng)態(tài)定義的。由于組合要求對(duì)象具有良好定義的接口,而且,對(duì)象只能通過接口訪問,所以我們并不破壞封裝性;只要類型一致,運(yùn)行時(shí)刻還可以用一個(gè)對(duì)象來替代另一個(gè)對(duì)象;更進(jìn)一步,因?yàn)閷?duì)象的實(shí)現(xiàn)是基于接口寫的,所以實(shí)現(xiàn)上存在較少的依賴關(guān)系。優(yōu)先使用對(duì)象組合有助于你保持每個(gè)類被封裝,并且只集中完成單個(gè)任務(wù)。這樣
34、類和類繼承層次會(huì)保持較小規(guī)模,并且不太可能增長(zhǎng)為不可控制的龐然大物(這正是濫用繼承的后果)。另一方面,基于對(duì)象組合的設(shè)計(jì)會(huì)有更多的對(duì)象(但只有較少的類),且系統(tǒng)的行為將依賴于對(duì)象間的關(guān)系而不是被定義在某個(gè)類中。注意:理想情況下,我們不用為獲得復(fù)用而去創(chuàng)建新的組件,只需要使用對(duì)象組合技術(shù),通過組裝已有的組件就能獲得需要的功能。但是事實(shí)很少如此,因?yàn)榭捎玫慕M件集合并不豐富。使用繼承的復(fù)用使得創(chuàng)建新的組件要比組裝已有的組件來得容易。這樣,繼承和對(duì)象組合常一起使用。然而,正如前面所說,千萬不要濫用繼承而忽視了對(duì)象組合技術(shù)。相關(guān)的設(shè)計(jì)模式有:Bridge、Composite、Decorator、Obse
35、rver、Strategy等。下面的例子演示了這個(gè)規(guī)則,它的前提是:我們對(duì)同一個(gè)數(shù)據(jù)結(jié)構(gòu),需要以任意的格式輸出。第一個(gè)例子,我們使用基于繼承的框架,可以看到,它很難維護(hù)和擴(kuò)展。abstract class AbstractExampleDocument/ skip some code .public void output(Example structure)if( null != structure )this.format( structure );protected void format(Example structure);第二個(gè)例子,我們使用基于對(duì)象組合技術(shù)的框架,每個(gè)對(duì)象的任務(wù)都
36、清楚的分離開來,我們可以替換、擴(kuò)展格式類,而不用考慮其它的任何事情。 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è)計(jì)的第三個(gè)原則。如果使用繼承的復(fù)用技術(shù),我們可以在抽象基類中定義好不可變的部分,而由其子類去具體實(shí)現(xiàn)可變的部分,不可變的部分不需要重復(fù)定義,而且便于維護(hù)。如果使用對(duì)象組合的復(fù)用技術(shù),我們可以定義好不可變的部分,而可變的部分可以由不同的組件實(shí)現(xiàn),根據(jù)需要,在運(yùn)行時(shí)動(dòng)態(tài)配置。這樣,我們就有更多的時(shí)間關(guān)注可變的部分。對(duì)于對(duì)象組合技術(shù)而言,每個(gè)組件只完成相對(duì)較小的功能,相互之間耦合比較松散,復(fù)用率較高,通過組合,就能獲得新的功能。4
38、、 減少方法的長(zhǎng)度 通常,我們的方法應(yīng)該只有盡量少的幾行,太長(zhǎng)的方法會(huì)難以理解,而且,如果方法太長(zhǎng),則應(yīng)該重新設(shè)計(jì)。對(duì)此,可以總結(jié)為以下原則: 三十秒原則:如果另一個(gè)程序員無法在三十秒之內(nèi)了解你的函數(shù)做了什么(What),如何做(How)以及為什么要這樣做(Why),那就說明你的代碼是難以維護(hù)的,必須得到提高; 一屏原則:如果一個(gè)函數(shù)的代碼長(zhǎng)度超過一個(gè)屏幕,那么或許這個(gè)函數(shù)太長(zhǎng)了,應(yīng)該拆分成更小的子函數(shù); 一行代碼盡量簡(jiǎn)短,并且保證一行代碼只做一件事那種看似技巧性的冗長(zhǎng)代碼只會(huì)增加代碼維護(hù)的難度。5、 消除case / if語句 要盡量避免在代碼中出現(xiàn)判斷語句,來測(cè)試一個(gè)對(duì)象是否某個(gè)特定類的實(shí)例。通常,如果你需要這么做,那么,重新設(shè)計(jì)可能會(huì)有所幫助。我在工作中遇到這樣的一個(gè)問題:我們?cè)谑褂肑AVA做XML解析時(shí),對(duì)每個(gè)標(biāo)簽映射了一個(gè)JAVA類,采用SAX(簡(jiǎn)單的XML接口API:Simple API for XML)模型。結(jié)果,代碼中反復(fù)出現(xiàn)了大量的判斷語句,來測(cè)試當(dāng)前的標(biāo)簽類型。為此,我們重新設(shè)計(jì)了DTD(文檔類型定義:Document Type Definition),為每個(gè)標(biāo)簽增加了一個(gè)固定的屬性:classname,而且重新設(shè)計(jì)了每個(gè)標(biāo)簽映射的JAVA類的接口,統(tǒng)一了每個(gè)對(duì)象的操作: addElement(Element aElement); /增加子元素addAt
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年汽車零部件供應(yīng)鏈合同樣本
- 2025年各類金融機(jī)構(gòu)借款合同示范文本
- 2025年住宅貸款抵押合同范本
- 高級(jí)測(cè)量專家性聘用協(xié)議樣本
- 2025年出租車掛靠經(jīng)營(yíng)合同樣本
- 2025年電動(dòng)牙刷區(qū)域代理合同
- 2025年二手房產(chǎn)交易合同范文標(biāo)準(zhǔn)版
- 2025年典型授權(quán)投資合同
- 2025年?;愤\(yùn)輸駕駛員合同樣本
- 2025年分店?duì)I業(yè)經(jīng)理聘請(qǐng)合同
- 小紅書種草營(yíng)銷師(初級(jí))認(rèn)證考試真題試題庫(含答案)
- 2025年中國(guó)洗衣凝珠行業(yè)市場(chǎng)現(xiàn)狀及投資態(tài)勢(shì)分析報(bào)告(智研咨詢)
- 企業(yè)資產(chǎn)管理培訓(xùn)
- 藥品經(jīng)營(yíng)使用和質(zhì)量監(jiān)督管理辦法2024年宣貫培訓(xùn)課件
- DB41T 2466-2023 浸水電梯使用管理規(guī)范
- 自然辯證法學(xué)習(xí)通超星期末考試答案章節(jié)答案2024年
- 2024年4月27日浙江省事業(yè)單位招聘《職業(yè)能力傾向測(cè)驗(yàn)》試題
- 物業(yè)管理服務(wù)應(yīng)急響應(yīng)方案
- 專題18 化學(xué)工藝流程綜合題-分離、提純類-五年(2020-2024)高考化學(xué)真題分類匯編(解析版)
- 汽車美容裝潢技術(shù)電子教案 6.3-汽車娛樂類電子產(chǎn)品裝飾
- 風(fēng)車的原理小班課件
評(píng)論
0/150
提交評(píng)論