什么是面向?qū)ο蠹夹g(shù)
面向?qū)ο蠹夹g(shù)是一種以對(duì)象為基礎(chǔ),以事件或消息來(lái)驅(qū)動(dòng)對(duì)象執(zhí)行處理的程序設(shè)計(jì)技術(shù)
面向?qū)ο笥心男┨匦?/h3>
面向?qū)ο蠹夹g(shù)具有抽象性、封裝性、繼承性、多態(tài)性
抽象
把眾多的事物進(jìn)行歸納、分類(lèi) 。是人們?cè)谡J(rèn)識(shí)客觀世界時(shí)經(jīng)常采用的思維方法,“物以類(lèi)聚,人以群分”就是分類(lèi)的意思,分類(lèi)所依據(jù)的原則是抽象。抽就是忽略事物中與當(dāng)前目標(biāo)無(wú)關(guān)的非本質(zhì)特征,更充分地注意與當(dāng)前目標(biāo)有關(guān)的本質(zhì)特征。從而找出事物的共性,并把具有共性的事物劃為一類(lèi),得到一個(gè)抽象的概念。
封裝
就是把對(duì)象的屬性和行為結(jié)合成一個(gè)獨(dú)立的單位,并盡可能隱蔽對(duì)象的內(nèi)部細(xì)節(jié)。
封裝有兩個(gè)含義:一是把對(duì)象的全部屬性和行為結(jié)合在一起,形成一個(gè)不可分割的獨(dú)立單位。對(duì)象的屬性值(除了公有的屬性值)只能由這個(gè)對(duì)象的行為來(lái)讀取和修改;
二是盡可能隱蔽對(duì)象的內(nèi)部細(xì)節(jié),對(duì)外形成一道屏障,與外部的聯(lián)系只能通過(guò)外部接口實(shí)現(xiàn)。將功能封裝成一個(gè)個(gè)獨(dú)立的單元,減小耦合,避免牽一發(fā)而動(dòng)全身,方便對(duì)程序的修改
繼承
客觀事物既有共性,也有特性。如果只考慮事物的共性,而不考慮事物的特性,就不能反映出客觀世界中事物之間的層次關(guān)系,不能完整地、正確地對(duì)客觀世界進(jìn)行抽象描述。運(yùn)用抽象的原則就是舍棄對(duì)象的特性,提取其共性,從而得到適合一個(gè)對(duì)象集的類(lèi)。如果在這個(gè)類(lèi)的基礎(chǔ)上,再考慮抽象過(guò)程中各對(duì)象被舍棄的那部分特性,則可形成一個(gè)新的類(lèi),這個(gè)類(lèi)具有前一個(gè)類(lèi)的全部特征,是前一個(gè)類(lèi)的子集,形成一種層次結(jié)構(gòu),即繼承結(jié)構(gòu)。 代碼重用,減少編碼量,間接減少維護(hù)成本。
多態(tài)
面向?qū)ο笤O(shè)計(jì)借鑒了客觀世界的多態(tài)性,體現(xiàn)在不同的對(duì)象收到相同的消息時(shí)產(chǎn)生多種不同的行為方式。
總結(jié)
面向?qū)ο蠹夹g(shù)強(qiáng)調(diào)在軟件開(kāi)發(fā)過(guò)程中面向客觀世界或問(wèn)題域中的事物,采用人類(lèi)在認(rèn)識(shí)客觀世界的過(guò)程中普遍運(yùn)用的思維方法,直觀、自然地描述客觀世界中的有關(guān)事物。
對(duì)象、類(lèi)、抽象類(lèi)、接口的意義
對(duì)象
對(duì)象是現(xiàn)實(shí)世界中的物理實(shí)體在計(jì)算機(jī)邏輯中的映射和體現(xiàn)
類(lèi)
類(lèi)是同種對(duì)象的集合與抽象
抽象類(lèi)
抽象類(lèi)是對(duì)類(lèi)的抽象
接口
接口是對(duì)行為的抽象
什么是面向過(guò)程
面向過(guò)程就是分析出解決問(wèn)題所需要的步驟,然后用函數(shù)把這些步驟一步一步實(shí)現(xiàn),使用的時(shí)候一個(gè)一個(gè)依次調(diào)用就可以了,它其實(shí)是最為實(shí)際的一種思考方式,就算是面向?qū)ο蟮姆椒ㄒ彩呛忻嫦蜻^(guò)程的思想.可以說(shuō)面向過(guò)程是一種基礎(chǔ)的方法.它考慮的是實(shí)際地實(shí)現(xiàn).一般的面向過(guò)程是從上往下步步求精.所以面向過(guò)程最重要的是模塊化的思想方法.
對(duì)比面向過(guò)程,面向?qū)ο蟮姆椒ㄖ饕前咽挛锝o對(duì)象化,對(duì)象包括屬性與行為.當(dāng)程序規(guī)模不是很大時(shí),
面向過(guò)程的方法還會(huì)體現(xiàn)出一種優(yōu)勢(shì),因?yàn)槌绦虻牧鞒毯芮宄?按著模塊與函數(shù)的方法可以很好的組織.
五子棋游戲設(shè)計(jì)思路
面向過(guò)程設(shè)計(jì)思路
1、開(kāi)始游戲
2、黑子先走
3、繪制畫(huà)面
4、判斷輸贏
5、輪到白子
6、繪制畫(huà)面
7、判斷輸贏
8、返回步驟2
9、輸出最后結(jié)果。
面向?qū)ο笤O(shè)計(jì)思路
1、黑白雙方,這兩方的行為是一模一樣的
2、棋盤(pán)系統(tǒng),負(fù)責(zé)繪制畫(huà)面,
3、規(guī)則系統(tǒng),負(fù)責(zé)判定諸如犯規(guī)、輸贏等。
加入悔棋功能的結(jié)果
現(xiàn)在要加入悔棋的功能,如果要改動(dòng)面向過(guò)程的設(shè)計(jì),那么從輸入到判斷到顯示這一連串的步驟都要改動(dòng),甚至步驟之間的循序都要進(jìn)行大規(guī)模調(diào)整。
如果是面向?qū)ο蟮脑?huà),只用改動(dòng)棋盤(pán)對(duì)象就行了,棋盤(pán)系統(tǒng)保存了黑白雙方的棋譜,簡(jiǎn)單回溯就可以了,而顯示和規(guī)則判斷則不用顧及,同時(shí)整個(gè)對(duì)對(duì)象功能的調(diào)用順序都沒(méi)有變化,改動(dòng)只是局部的。由此可以看出面向?qū)ο蟾子跀U(kuò)展。
面向?qū)ο蠛兔嫦蜻^(guò)程的不同之處
首先,我們使用面向過(guò)程想的是什么呀?先想的是游戲的步驟,怎么一步步的走下去對(duì)吧。
那使用面向?qū)ο竽兀?我們先想的是有哪些設(shè)計(jì)的對(duì)象,然后這些對(duì)象都做了些什么,具有什么行為,最后再按照面向過(guò)程的思想把行為一步步組織起來(lái)是吧。面向過(guò)程中重的是函數(shù),這個(gè)函數(shù)做什么,那個(gè)做什么,對(duì)于數(shù)據(jù)是和函數(shù)分開(kāi)的。面向?qū)ο笫前褦?shù)據(jù)和函數(shù)封裝在一起。所以我們可以總結(jié)一下簡(jiǎn)單區(qū)別如下:
1.面向過(guò)程采用函數(shù)(或過(guò)程)來(lái)描述對(duì)數(shù)據(jù)的操作,但將函數(shù)與其操作的數(shù)據(jù)分離開(kāi);面向?qū)ο髮?shù)據(jù)和對(duì)數(shù)據(jù)的操作封裝在一起
2.面向過(guò)程以功能為中心設(shè)計(jì)功能模塊,難維護(hù);面向?qū)ο笠詳?shù)據(jù)為中心來(lái)描述系統(tǒng),數(shù)據(jù)相對(duì)于功能而言具有較強(qiáng)的穩(wěn)定性,易維護(hù)。
我們分析了面向?qū)ο蠛兔嫦蜻^(guò)程,并通過(guò)五子棋的設(shè)計(jì)簡(jiǎn)單闡述了二者的區(qū)別。接下來(lái)我們看看如何軟件設(shè)計(jì)有什么需要注意的。
什么是軟件設(shè)計(jì)
軟件設(shè)計(jì)是從軟件需求規(guī)格說(shuō)明書(shū)出發(fā),根據(jù)需求分析階段確定的功能設(shè)計(jì)軟件系統(tǒng)的整體結(jié)構(gòu)、劃分功能模塊、確定每個(gè)模塊的實(shí)現(xiàn)算法以及編寫(xiě)具體的代碼,形成軟件的具體設(shè)計(jì)方案。
什么是好的設(shè)計(jì)
好的設(shè)計(jì)是容易擴(kuò)展的(軟件產(chǎn)品需求變更和擴(kuò)充經(jīng)常的事情,在變化來(lái)領(lǐng)的時(shí)候,我們的軟件結(jié)構(gòu)如何能從容不迫的面對(duì)這些變化,決定了你設(shè)計(jì)的高度)
好的設(shè)計(jì)是容易維護(hù)的(項(xiàng)目結(jié)束后依然要維護(hù),修復(fù)使用過(guò)程中發(fā)現(xiàn)的bug以及人員變換等等,都對(duì)軟件的可維護(hù)性要求較高)
好的設(shè)計(jì)是容易移植的(市面上類(lèi)似的軟件很多很多,當(dāng)你對(duì)不同客戶(hù)開(kāi)發(fā)一款類(lèi)似的軟件,那么所有的東西再做一遍么? 那成本多高,很多時(shí)候,我們會(huì)考慮復(fù)用項(xiàng)目中的功能模塊和代碼,所以容易移植和復(fù)用也是衡量一個(gè)軟件設(shè)計(jì)好壞與否的重要指標(biāo))
7大類(lèi)與接口的設(shè)計(jì)原則
單一職責(zé)原則
一個(gè)類(lèi)只負(fù)責(zé)一項(xiàng)職責(zé).
兩種常見(jiàn)的情況是:
一種是職責(zé)發(fā)散
當(dāng)某一功能發(fā)生修改的時(shí),結(jié)果修改了很多類(lèi)。修改越多的類(lèi),造成的風(fēng)險(xiǎn)越大,因?yàn)轭?lèi)之間的功能模塊互相影響。
第二種是承擔(dān)太多職責(zé)
一個(gè)類(lèi)有功能F1,結(jié)果因?yàn)槟撤N情況增加了F2.F!和F2中可能有共同使用的數(shù)據(jù)。對(duì)F1進(jìn)行修改的時(shí)候有可能影響F2的功能。
這樣可以添加一個(gè)新類(lèi),把F2移到這個(gè)新類(lèi)當(dāng)中。
單一職責(zé)的優(yōu)勢(shì)是:
降低類(lèi)的復(fù)雜度,一個(gè)類(lèi)只負(fù)責(zé)一項(xiàng)職責(zé),其邏輯肯定要比負(fù)責(zé)多項(xiàng)職責(zé)簡(jiǎn)單的多;
提高類(lèi)的可讀性,提高系統(tǒng)的可維護(hù)性;
變更引起的風(fēng)險(xiǎn)降低,變更是必然的,如果單一職責(zé)原則遵守的好,當(dāng)修改一個(gè)功能時(shí),可以顯著降低對(duì)其他功能的影響。
迪米特原則
一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象保持最少的了解。
問(wèn)題由來(lái)
類(lèi)與類(lèi)之間的關(guān)系越密切,耦合度越大,當(dāng)一個(gè)類(lèi)發(fā)生改變時(shí),對(duì)另一個(gè)類(lèi)的影響也越大。
解決方案
盡量降低類(lèi)與類(lèi)之間的耦合。
自從我們接觸編程開(kāi)始,就知道了軟件編程的總的原則:低耦合,高內(nèi)聚。無(wú)論是面向過(guò)程編程還是面向?qū)ο缶幊?,只有使各個(gè)模塊之間的耦合盡量的低,才能提高代碼的復(fù)用率。低耦合的優(yōu)點(diǎn)不言而喻,但是怎么樣編程才能做到低耦合呢?那正是迪米特法則要去完成的。
迪米特法則又叫最少知道原則,就是一個(gè)類(lèi)對(duì)自己依賴(lài)的類(lèi)知道的越少越好。也就是說(shuō),對(duì)于被依賴(lài)的類(lèi)來(lái)說(shuō),無(wú)論邏輯多么復(fù)雜,都盡量地的將邏輯封裝在類(lèi)的內(nèi)部,對(duì)外除了提供的public方法,不對(duì)外泄漏任何信息。迪米特法則還有一個(gè)更簡(jiǎn)單的定義:只與直接的朋友通信。首先來(lái)解釋一下什么是直接的朋友:每個(gè)對(duì)象都會(huì)與其他對(duì)象有耦合關(guān)系,只要兩個(gè)對(duì)象之間有耦合關(guān)系,我們就說(shuō)這兩個(gè)對(duì)象之間是朋友關(guān)系。耦合的方式很多,依賴(lài)、關(guān)聯(lián)、組合、聚合等。其中,我們稱(chēng)出現(xiàn)成員變量、方法參數(shù)、方法返回值中的類(lèi)為直接的朋友,而出現(xiàn)在局部變量中的類(lèi)則不是直接的朋友。也就是說(shuō),陌生的類(lèi)最好不要作為局部變量的形式出現(xiàn)在類(lèi)的內(nèi)部。
里氏替換原則
所有引用基類(lèi)的地方必須能透明地使用其子類(lèi)的對(duì)象。
定義1
如果對(duì)每一個(gè)類(lèi)型為 T1的對(duì)象 o1,都有類(lèi)型為 T2 的對(duì)象o2,使得以 T1定義的所有程序 P 在所有的對(duì)象 o1 都代換成 o2 時(shí),程序 P 的行為沒(méi)有發(fā)生變化,那么類(lèi)型 T2 是類(lèi)型 T1 的子類(lèi)型。
定義2
所有引用基類(lèi)的地方必須能透明地使用其子類(lèi)的對(duì)象。
問(wèn)題由來(lái)
有一功能P1,由類(lèi)A完成。現(xiàn)需要將功能P1進(jìn)行擴(kuò)展,擴(kuò)展后的功能為P,其中P由原有功能P1與新功能P2組成。新功能P由類(lèi)A的子類(lèi)B來(lái)完成,則子類(lèi)B在完成新功能P2的同時(shí),有可能會(huì)導(dǎo)致原有功能P1發(fā)生故障。
解決方案
當(dāng)使用繼承時(shí),遵循里氏替換原則。類(lèi)B繼承類(lèi)A時(shí),除添加新的方法完成新增功能P2外,盡量不要重寫(xiě)父類(lèi)A的方法,也盡量不要重載父類(lèi)A的方法。
繼承包含這樣一層含義:父類(lèi)中凡是已經(jīng)實(shí)現(xiàn)好的方法(相對(duì)于抽象方法而言),實(shí)際上是在設(shè)定一系列的規(guī)范和契約,雖然它不強(qiáng)制要求所有的子類(lèi)必須遵從這些契約,但是如果子類(lèi)對(duì)這些非抽象方法任意修改,就會(huì)對(duì)整個(gè)繼承體系造成破壞。而里氏替換原則就是表達(dá)了這一層含義。
繼承作為面向?qū)ο笕筇匦灾?,在給程序設(shè)計(jì)帶來(lái)巨大便利的同時(shí),也帶來(lái)了弊端。比如使用繼承會(huì)給程序帶來(lái)侵入性,程序的可移植性降低,增加了對(duì)象間的耦合性,如果一個(gè)類(lèi)被其他的類(lèi)所繼承,則當(dāng)這個(gè)類(lèi)需要修改時(shí),必須考慮到所有的子類(lèi),并且父類(lèi)修改后,所有涉及到子類(lèi)的功能都有可能會(huì)產(chǎn)生故障。
里氏替換原則通俗的來(lái)講就是:子類(lèi)可以擴(kuò)展父類(lèi)的功能,但不能改變父類(lèi)原有的功能。它包含以下4層含義:
1.子類(lèi)可以實(shí)現(xiàn)父類(lèi)的抽象方法,但不能覆蓋父類(lèi)的非抽象方法。
2.子類(lèi)中可以增加自己特有的方法。
3.當(dāng)子類(lèi)的方法重載父類(lèi)的方法時(shí),方法的前置條件(即方法的形參)要比父類(lèi)方法的輸入?yún)?shù)更寬松。
4.當(dāng)子類(lèi)的方法實(shí)現(xiàn)父類(lèi)的抽象方法時(shí),方法的后置條件(即方法的返回值)要比父類(lèi)更嚴(yán)格。
合成組合原則
盡量使用對(duì)象組合,而不是繼承來(lái)達(dá)到復(fù)用的目的。
核心思想
盡量使用對(duì)象組合,而不是繼承來(lái)達(dá)到復(fù)用的目的。該原則就是在一個(gè)新的對(duì)象里面使用一些已有的對(duì)象,使之成為新對(duì)象的一部分:新的對(duì)象通過(guò)向這些對(duì)象的委派達(dá)到復(fù)用已有功能的目的。
術(shù)語(yǔ):
(1)聚合(Aggregation):聚合用來(lái)表示“擁有”關(guān)系或者整體與部分的關(guān)系;
(2)合成(Composition):合成則用來(lái)表示一種強(qiáng)得 多的“擁有”關(guān)系。在一個(gè)合成關(guān)系里面,部分和整體的生命周期是一樣的。
復(fù)用的種類(lèi):
繼承
優(yōu)點(diǎn): 新的實(shí)現(xiàn)較為容易,因?yàn)榛?lèi)的大部分功能可以通過(guò)繼承關(guān)系自動(dòng)進(jìn)入派生類(lèi); 修改或擴(kuò)展繼承而來(lái)的實(shí)現(xiàn)較為容易
缺點(diǎn): 繼承復(fù)用破壞包裝,因?yàn)槔^承將基類(lèi)的實(shí)現(xiàn)細(xì)節(jié)暴露給派生類(lèi),這種復(fù)用也稱(chēng)為白箱復(fù)用; 如果基類(lèi)的實(shí)現(xiàn)發(fā)生改變,那么派生類(lèi)的實(shí)現(xiàn)也不得不發(fā)生改變; 從基類(lèi)繼承而來(lái)的實(shí)現(xiàn)是靜態(tài)的,不可能在運(yùn)行時(shí)發(fā)生改變,不夠靈活。
合成聚合
優(yōu)點(diǎn): 新對(duì)象存取成分對(duì)象的唯一方法是通過(guò)成分對(duì)象的接口; 這種復(fù)用是黑箱復(fù)用,因?yàn)槌煞謱?duì)象的內(nèi)部細(xì)節(jié)是新對(duì)象所看不見(jiàn)的; 這種復(fù)用支持包裝; 這種復(fù)用所需的依賴(lài)較少; 每一個(gè)新的類(lèi)可以將焦點(diǎn)集中在一個(gè)任務(wù)上; 這種復(fù)用可以在運(yùn)行時(shí)動(dòng)態(tài)進(jìn)行,新對(duì)象可以使用合成/聚合關(guān)系將新的責(zé)任委派到合適的對(duì)象。
缺點(diǎn): 通過(guò)這種方式復(fù)用建造的系統(tǒng)會(huì)有較多的對(duì)象需要管理。
如何選擇繼承或合成聚合
在復(fù)用時(shí)應(yīng)優(yōu)先考慮使用合成聚合而不是繼承,而判定的判斷為以下四個(gè)Coad條件:
1.派生類(lèi)是基類(lèi)的一個(gè)特殊種類(lèi),而不是基類(lèi)的一個(gè)角色,即要分清"Has-A"和"Is-A"的區(qū)別;
2.永遠(yuǎn)不會(huì)出現(xiàn)需要將派生類(lèi)換成另一個(gè)類(lèi)的派生類(lèi)的情況;
3.派生類(lèi)具有擴(kuò)展基類(lèi)的責(zé)任,而不是具有置換或者注銷(xiāo)掉基類(lèi)的責(zé)任;
4.只有在分類(lèi)學(xué)角度有意義時(shí),才可以使用繼承。
接口隔離原則
定義
客戶(hù)端不應(yīng)該依賴(lài)它不需要的接口;一個(gè)類(lèi)對(duì)另一個(gè)類(lèi)的依賴(lài)應(yīng)該建立在最小的接口上。
問(wèn)題由來(lái)
類(lèi)A通過(guò)接口I依賴(lài)類(lèi)B,類(lèi)C通過(guò)接口I依賴(lài)類(lèi)D,如果接口I對(duì)于類(lèi)A和類(lèi)B來(lái)說(shuō)不是最小接口,則類(lèi)B和類(lèi)D必須去實(shí)現(xiàn)他們不需要的方法。
解決方案
將臃腫的接口I拆分為獨(dú)立的幾個(gè)接口,類(lèi)A和類(lèi)C分別與他們需要的接口建立依賴(lài)關(guān)系。也就是采用接口隔離原則。
接口盡量小,但是要有限度。對(duì)接口進(jìn)行細(xì)化可以提高程序設(shè)計(jì)靈活性是不掙的事實(shí),但是如果過(guò)小,則會(huì)造成接口數(shù)量過(guò)多,使設(shè)計(jì)復(fù)雜化。所以一定要適度。
為依賴(lài)接口的類(lèi)定制服務(wù),只暴露給調(diào)用的類(lèi)它需要的方法,它不需要的方法則隱藏起來(lái)。只有專(zhuān)注地為一個(gè)模塊提供定制服務(wù),才能建立最小的依賴(lài)關(guān)系。
提高內(nèi)聚,減少對(duì)外交互。使接口用最少的方法去完成最多的事情。
開(kāi)閉原則
定義
一個(gè)軟件實(shí)體如類(lèi)、模塊和函數(shù)應(yīng)該對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。
問(wèn)題由來(lái)
在軟件的生命周期內(nèi),因?yàn)樽兓⑸?jí)和維護(hù)等原因需要對(duì)軟件原有代碼進(jìn)行修改時(shí),可能會(huì)給舊代碼中引入錯(cuò)誤,也可能會(huì)使我們不得不對(duì)整個(gè)功能進(jìn)行重構(gòu),并且需要原有代碼經(jīng)過(guò)重新測(cè)試。
解決方案
當(dāng)軟件需要變化時(shí),盡量通過(guò)擴(kuò)展軟件實(shí)體的行為來(lái)實(shí)現(xiàn)變化,而不是通過(guò)修改已有的代碼來(lái)實(shí)現(xiàn)變化。
開(kāi)閉原則是面向?qū)ο笤O(shè)計(jì)中最基礎(chǔ)的設(shè)計(jì)原則,它指導(dǎo)我們?nèi)绾谓⒎€(wěn)定靈活的系統(tǒng)。開(kāi)閉原則可能是設(shè)計(jì)模式幾項(xiàng)原則中定義最模糊的一個(gè)了,它只告訴我們對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉,可是到底如何才能做到對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉,并沒(méi)有明確的告訴我們。以前,如果有人告訴我“你進(jìn)行設(shè)計(jì)的時(shí)候一定要遵守開(kāi)閉原則”,我會(huì)覺(jué)的他什么都沒(méi)說(shuō),但貌似又什么都說(shuō)了。因?yàn)殚_(kāi)閉原則真的太虛了。
在仔細(xì)思考以及仔細(xì)閱讀很多設(shè)計(jì)模式的文章后,終于對(duì)開(kāi)閉原則有了一點(diǎn)認(rèn)識(shí)。其實(shí),我們遵循設(shè)計(jì)模式前面幾大原則,以及使用23種設(shè)計(jì)模式的目的就是遵循開(kāi)閉原則。也就是說(shuō),只要我們對(duì)前面5項(xiàng)原則遵守的好了,設(shè)計(jì)出的軟件自然是符合開(kāi)閉原則的,這個(gè)開(kāi)閉原則更像是前面五項(xiàng)原則遵守程度的“平均得分”,前面幾項(xiàng)原則遵守的好,平均分自然就高,說(shuō)明軟件設(shè)計(jì)開(kāi)閉原則遵守的好;如果前面5項(xiàng)原則遵守的不好,則說(shuō)明開(kāi)閉原則遵守的不好。
說(shuō)到這里,再回想一下前面說(shuō)的幾項(xiàng)原則,恰恰是告訴我們用抽象構(gòu)建框架,用實(shí)現(xiàn)擴(kuò)展細(xì)節(jié)的注意事項(xiàng)而已:?jiǎn)我宦氊?zé)原則告訴我們實(shí)現(xiàn)類(lèi)要職責(zé)單一;里氏替換原則告訴我們不要破壞繼承體系;依賴(lài)倒置原則告訴我們要面向接口編程;接口隔離原則告訴我們?cè)谠O(shè)計(jì)接口的時(shí)候要精簡(jiǎn)單一;迪米特法則告訴我們要降低耦合。而開(kāi)閉原則是總綱,他告訴我們要對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。
6大包設(shè)計(jì)原則
重用發(fā)布原則
重用的粒度就是發(fā)布的粒度:一個(gè)包中的軟件要么都是可重用的,要么都是不可重用的。
代碼可以看作為可重用的代碼,當(dāng)且僅當(dāng):
- 它的使用者(下稱(chēng)用戶(hù))無(wú)需看它的源代碼
- 用戶(hù)只需聯(lián)結(jié)靜態(tài)庫(kù)或包含動(dòng)態(tài)庫(kù)
- 當(dāng)庫(kù)發(fā)生改變(錯(cuò)誤糾正,功能增強(qiáng))時(shí),用戶(hù)只需要得到一個(gè)新的版本便能集成到原有的系統(tǒng)
怎么做到重用呢?
一個(gè)組件要做到能夠重用,它必須有一個(gè)得到良好設(shè)計(jì)的結(jié)構(gòu),它所包含所有元素必須也是可以重用的。
因?yàn)槿绻粋€(gè)為重用而設(shè)計(jì)的發(fā)布單位里,包含了不可重用的元素,當(dāng)不可重用的元素發(fā)生改變時(shí),用戶(hù)也不得不改變?cè)邢到y(tǒng)以適應(yīng)新的版本。這顯然違反了重用的定義規(guī)則。
也就是說(shuō),一個(gè)為重用目的而設(shè)計(jì)的發(fā)布單位里,不能包含不可重用的元素;如果包含了不可重用的元素,它將變得不可重用。
共同重用原則
一個(gè)包中所有類(lèi)應(yīng)該是共同重用的。如果重用了包中的一個(gè)類(lèi),那么就重用包中的所有類(lèi)。
這個(gè)原則可以幫助我們決定哪些類(lèi)應(yīng)該放在同一個(gè)包中。它規(guī)定了趨向共同重用的類(lèi)應(yīng)該屬于同一個(gè)包。
類(lèi)很少會(huì)孤立地重用。一般來(lái)說(shuō),可重用的類(lèi)需要與作為該可重用抽象一部分的其他類(lèi)協(xié)作。CRP規(guī)定了這些類(lèi)應(yīng)該屬于同一個(gè)包。在這樣的一個(gè)包中,我們會(huì)看到類(lèi)之間有很多的互相依賴(lài)。
一個(gè)簡(jiǎn)單的例子是容器類(lèi)以及與它關(guān)聯(lián)的迭代器類(lèi)。這些類(lèi)彼此之間緊密耦合在一起,因此必須共同重用。所以它們應(yīng)該在同一個(gè)包中
但是,CRP告訴我們的不僅僅是什么類(lèi)應(yīng)該共同放入一個(gè)包中。它還告訴我們什么類(lèi)不應(yīng)該放入同一個(gè)包中。當(dāng)一個(gè)包使用了另一個(gè)包時(shí),它們之間會(huì)存在一個(gè) 依賴(lài)關(guān)系。也許一個(gè)包僅僅使用了另外一個(gè)包中的一個(gè)類(lèi)。然而,那根本不會(huì)削弱這兩個(gè)包之間的依賴(lài)關(guān)系。使用者包依然依賴(lài)于被使用的包。每當(dāng)被使用的包發(fā)布 時(shí),使用者包必須進(jìn)行重新驗(yàn)證和重新發(fā)布。即使發(fā)布的原因僅僅改變了一個(gè)使用者包根本不關(guān)心的類(lèi),也必須要這樣做。
此 外,包也經(jīng)常以共享庫(kù)、DLL、JAR等物理表示的形式出現(xiàn)。如果被使用的包以JAR的形式發(fā)布,那么使用這個(gè)包的代碼就依賴(lài)于整個(gè)JAR。對(duì)JAR的任 何修改——即使所修改的是與用戶(hù)代碼無(wú)關(guān)的類(lèi),也會(huì)造成這個(gè)JAR的一個(gè)新版本發(fā)布。這個(gè)新JAR也要重新發(fā)行,并且,使用這個(gè)JAR的代碼也要進(jìn)行重新 驗(yàn)證。
共同封閉原則
這是單一職責(zé)原則(SRP)對(duì)于包的重新規(guī)定。正如SRP規(guī)定的一個(gè)類(lèi)不應(yīng)該包含多個(gè)引起變化的原因那樣,這個(gè)原則規(guī)定了一個(gè)包不應(yīng)該包含多個(gè)引起變化的原因。
在大多數(shù)的應(yīng)用中,可維護(hù)性的重要性是超過(guò)可重用性的。如果一個(gè)應(yīng)用中的代碼必須更改,那么我們寧愿更改都集中在一個(gè)包中,而不是分布在多個(gè)包中。如果 更改集中在一個(gè)單一的包中,那么我們僅僅需要發(fā)布那一個(gè)更改了的包。不依賴(lài)于那個(gè)更改了的包的其他包則不需要重新驗(yàn)證或重新發(fā)布。
CCP鼓勵(lì)我們把可能由于同樣的原因而改變的所有類(lèi)共同聚集在同一個(gè)地方。如果兩個(gè)類(lèi)之間有非常緊密的綁定關(guān)系,不管是物理上的還是概念上的,那么它們總會(huì)一同進(jìn)行變化,因而它們應(yīng)該屬于同一個(gè)包中,這樣做會(huì)減少軟件的發(fā)布、重新驗(yàn)證、重新發(fā)行的工作量。
這個(gè)原則和開(kāi)放-封閉原則(OCP)密切相關(guān)。本原則中“封閉”這個(gè)詞和OCP中的具有同樣的含義。OCP規(guī)定了類(lèi)對(duì)于修改應(yīng)該是封閉的,對(duì)于擴(kuò)展應(yīng)該是開(kāi)放的。但是,正如我們所學(xué)到的,100%的封閉是不可能的。應(yīng)當(dāng)進(jìn)行有策略的封閉。我們所設(shè)計(jì)的系統(tǒng)應(yīng)該對(duì)于我們經(jīng)歷過(guò)的最常見(jiàn)的變化做到封閉。
CCP通過(guò)把對(duì)于一些確定的變化類(lèi)型開(kāi)放的類(lèi)共同組織到同一個(gè)包中,從而增強(qiáng)了上述內(nèi)容。因而,當(dāng)需求中的一個(gè)變化到來(lái)時(shí),那個(gè)變化就會(huì)很有可能被限制在最小數(shù)量的包中。
無(wú)環(huán)依賴(lài)原則
在包的依賴(lài)圖中,不允許存在環(huán)。
穩(wěn)定依賴(lài)原則
朝著穩(wěn)定的方向進(jìn)行依賴(lài)
穩(wěn)定抽象原則
包的抽象程度應(yīng)該和其穩(wěn)定程度一致
該原則把包的穩(wěn)定性和抽象性聯(lián)系起來(lái)。它規(guī)定,一個(gè)穩(wěn)定的包應(yīng)該也是抽象的,這樣它的穩(wěn)定性就不會(huì)使其無(wú)法擴(kuò)展。另一方面,它規(guī)定,一個(gè)不穩(wěn)定的包應(yīng)該是具體的,因?yàn)樗牟环€(wěn)定性使得其內(nèi)部具體代碼易于更改。
因此,如果一個(gè)包是穩(wěn)定的,那么它應(yīng)該也要包含一些抽象類(lèi),這樣就可以對(duì)它進(jìn)行擴(kuò)展??蓴U(kuò)展的穩(wěn)定包是靈活的,并且不會(huì)過(guò)分限制設(shè)計(jì)。
SAP和SDP結(jié)合在一起形成了針對(duì)包的DIP原則。這樣說(shuō)是準(zhǔn)確的,因?yàn)镾DP規(guī)定依賴(lài)應(yīng)該朝著穩(wěn)定的方向進(jìn)行,而SAP則規(guī)定穩(wěn)定性意味著抽象性。因此,依賴(lài)應(yīng)該朝著抽象的方向進(jìn)行。
然而,DIP是一個(gè)處理類(lèi)的原則。類(lèi)沒(méi)有灰度(the shades of grey)的概念。一個(gè)類(lèi)要么是抽象的,要么不是。SDP和SAP的結(jié)合是處理包的,并且允許一個(gè)包是部分抽象、部分穩(wěn)定的。