這篇文章主要講的是面向?qū)ο笤O(shè)計中驮配,我們應(yīng)該遵循的六大原則。只有掌握了這些原則着茸,我們才能更好的理解設(shè)計模式壮锻。我們接下來要介紹以下6個內(nèi)容。
單一職責(zé)原則——SRP
開閉原則——OCP
里式替換原則——LSP
依賴倒置原則——DIP
接口隔離原則——ISP
迪米特原則——LOD
單一職責(zé)原則
單一職責(zé)原則的定義是就一個類而言涮阔,應(yīng)該僅有一個引起他變化的原因猜绣。也就是說一個類應(yīng)該只負(fù)責(zé)一件事情敬特。如果一個類負(fù)責(zé)了方法M1,方法M2兩個不同的事情掰邢,當(dāng)M1方法發(fā)生變化的時候伟阔,我們需要修改這個類的M1方法,但是這個時候就有可能導(dǎo)致M2方法不能工作皱炉。這個不是我們期待的,但是由于這種設(shè)計卻很有可能發(fā)生奏夫。所以這個時候,我們需要把M1方法酗昼,M2方法單獨(dú)分離成兩個類。讓每個類只專心處理自己的方法麻削。
單一職責(zé)原則的好處如下:
可以降低類的復(fù)雜度,一個類只負(fù)責(zé)一項職責(zé)呛哟,這樣邏輯也簡單很多 提高類的可讀性,和系統(tǒng)的維護(hù)性榛鼎,因為不會有其他奇怪的方法來干擾我們理解這個類的含義 當(dāng)發(fā)生變化的時候鳖孤,能將變化的影響降到最小者娱,因為只會在這個類中做出修改苏揣。
開閉原則
開閉原則和單一職責(zé)原則一樣,是非晨蚬担基礎(chǔ)而且一般是常識的原則。開閉原則的定義是軟件中的對象(類忍燥,模塊隙姿,函數(shù)等)應(yīng)該對于擴(kuò)展是開放的灾前,但是對于修改是關(guān)閉的孟辑。
當(dāng)需求發(fā)生改變的時候蔫敲,我們需要對代碼進(jìn)行修改,這個時候我們應(yīng)該盡量去擴(kuò)展原來的代碼奈嘿,而不是去修改原來的代碼,因為這樣可能會引起更多的問題尽狠。
這個準(zhǔn)則和單一職責(zé)原則一樣衔憨,是一個大家都這樣去認(rèn)為但是又沒規(guī)定具體該如何去做的一種原則袄膏。
開閉原則我們可以用一種方式來確保他,我們用抽象去構(gòu)建框架码党,用實現(xiàn)擴(kuò)展細(xì)節(jié)。這樣當(dāng)發(fā)生修改的時候揖盘,我們就直接用抽象了派生一個具體類去實現(xiàn)修改锌奴。
里氏替換原則
里氏替換原則是一個非常有用的一個概念兽狭。他的定義
如果對每一個類型為T1的對象o1,都有類型為T2的對象o2,使得以T1定義的所有程序P在所有對象o1都替換成o2的時候鹿蜀,程序P的行為都沒有發(fā)生變化,那么類型T2是類型T1的子類型销钝。
這樣說有點(diǎn)復(fù)雜,其實有一個簡單的定義
所有引用基類的地方必須能夠透明地使用其子類的對象蒸健。
里氏替換原則通俗的去講就是:子類可以去擴(kuò)展父類的功能婉商,但是不能改變父類原有的功能。他包含以下幾層意思:
子類可以實現(xiàn)父類的抽象方法丈秩,但是不能覆蓋父類的非抽象方法。
子類可以增加自己獨(dú)有的方法蘑秽。
當(dāng)子類的方法重載父類的方法時候,方法的形參要比父類的方法的輸入?yún)?shù)更加寬松肠牲。
當(dāng)子類的方法實現(xiàn)父類的抽象方法時,方法的返回值要比父類更嚴(yán)格渡嚣。
里氏替換原則之所以這樣要求是因為繼承有很多缺點(diǎn),他雖然是復(fù)用代碼的一種方法识椰,但同時繼承在一定程度上違反了封裝。父類的屬性和方法對子類都是透明的腹鹉,子類可以隨意修改父類的成員。這也導(dǎo)致了墓赴,如果需求變更,子類對父類的方法進(jìn)行一些復(fù)寫的時候诫硕,其他的子類無法正常工作刊侯。所以里氏替換法則被提出來章办。
確保程序遵循里氏替換原則可以要求我們的程序建立抽象滨彻,通過抽象去建立規(guī)范,然后用實現(xiàn)去擴(kuò)展細(xì)節(jié)亭饵,這個是不是很耳熟,對踏兜,里氏替換原則和開閉原則往往是相互依存的八秃。
依賴倒置原則
依賴倒置原則指的是一種特殊的解耦方式碱妆,使得高層次的模塊不應(yīng)該依賴于低層次的模塊的實現(xiàn)細(xì)節(jié)的目的昔驱,依賴模塊被顛倒了。這也是一個讓人難懂的定義纳本,他可以簡單來說就是
高層模塊不應(yīng)該依賴底層模塊,兩者都應(yīng)該依賴其抽象 抽象不應(yīng)該依賴細(xì)節(jié) 細(xì)節(jié)應(yīng)該依賴抽象
在Java 中抽象指的是接口或者抽象類饮醇,兩者皆不能實例化秕豫。而細(xì)節(jié)就是實現(xiàn)類,也就是實現(xiàn)了接口或者繼承了抽象類的類混移。他是可以被實例化的。高層模塊指的是調(diào)用端歌径,底層模塊是具體的實現(xiàn)類。在Java中回铛,依賴倒置原則是指模塊間的依賴是通過抽象來發(fā)生的,實現(xiàn)類之間不發(fā)生直接的依賴關(guān)系腔长,其依賴關(guān)系是通過接口是來實現(xiàn)的验残。這就是俗稱的面向接口編程捞附。
我們下面有一個例子來講述這個問題您没。這個例子是工人用錘子來修理東西。我們的代碼如下:
public?class?Hammer?{
public?String?function(){
return?"用錘子修理東西";
}
}
public?class?Worker?{
public?void?fix(Hammer?hammer){
System.out.println("工人"?+?hammer.function());
}
public?static?void?main(String[]?args)?{
new?Worker().fix(new?Hammer());
}
}
這個是一個很簡單的例子欧募,但是如果我們要新增加一個功能仆抵,工人用 螺絲刀來修理東西跟继,在這個類肢础,我們發(fā)現(xiàn)是很難做的。因為我們Worker類依賴于一個具體的實現(xiàn)類Hammer传轰。所以我們用到面向接口編程的思想,改成如下的代碼:
public?interface?Tools?{
public?String?function();
}
然后我們的Worker是通過這個接口來于其他細(xì)節(jié)類進(jìn)行依賴辽聊。代碼如下:
public?class?Worker?{
public?void?fix(Tools?tool){
System.out.println("工人"?+?tool.function());
}
public?static?void?main(String[]?args)?{
new?Worker().fix(new?Hammer());
new?Worker().fix(new?Screwdriver());
}
}
我們的Hammer類與Screwdriver類實現(xiàn)這個接口
public?class?Hammer?implements?Tools{
public?String?function(){
return?"用錘子修理東西";
}
}
public?class?Screwdriver?implements?Tools{
@Override
public?String?function()?{
return?"用螺絲刀修理東西";
}
}
這樣期贫,通過面向接口編程,我們的代碼就有了很高的擴(kuò)展性通砍,降低了代碼之間的耦合度烤蜕,提高了系統(tǒng)的穩(wěn)定性迹冤。
接口隔離原則
接口隔離原則的定義是
客戶端不應(yīng)該依賴他不需要的接口
換一種說法就是類間的依賴關(guān)系應(yīng)該建立在最小的接口上。這樣說好像更難懂泡徙。我們通過一個例子來說明。我們知道在Java中一個具體類實現(xiàn)了一個接口堪藐,那必然就要實現(xiàn)接口中的所有方法。如果我們有一個類A和類B通過接口I來依賴糖荒,類B是對類A依賴的實現(xiàn),這個接口I有5個方法寂嘉。但是類A與類B只通過方法1,2,3依賴枫绅,然后類C與類D通過接口I來依賴,類D是對類C依賴的實現(xiàn)但是他們卻是通過方法1,4,5依賴并淋。那么是必在實現(xiàn)接口的時候,類B就要有實現(xiàn)他不需要的方法4和方法5 而類D就要實現(xiàn)他不需要的方法2县耽,和方法3。這簡直就是一個災(zāi)難的設(shè)計兔毙。
所以我們需要對接口進(jìn)行拆分,就是把接口分成滿足依賴關(guān)系的最小接口锡溯,類B與類D不需要去實現(xiàn)與他們無關(guān)接口方法哑姚。比如在這個例子中祭饭,我們可以把接口拆成3個叙量,第一個是僅僅由方法1的接口,第二個接口是包含2,3方法的绞佩,第三個接口是包含4,5方法的猪钮。這樣胆建,我們的設(shè)計就滿足了接口隔離原則。
以上這些設(shè)計思想用英文的第一個字母可以組成SOLID 眼坏,滿足這個5個原則的程序也被稱為滿足了SOLID準(zhǔn)則酸些。
迪米特原則
迪米特原則也被稱為最小知識原則,他的定義
一個對象應(yīng)該對其他對象保持最小的了解魄懂。
因為類與類之間的關(guān)系越密切,耦合度越大缀拭,當(dāng)一個類發(fā)生改變時填帽,對另一個類的影響也越大蛛淋,所以這也是我們提倡的軟件編程的總的原則:低耦合篡腌,高內(nèi)聚。迪米特法則還有一個更簡單的定義
只與直接的朋友通信叛甫。首先來解釋一下什么是直接的朋友:每個對象都會與其他對象有耦合關(guān)系,只要兩個對象之間有耦合關(guān)系其监,我們就說這兩個對象之間是朋友關(guān)系限匣。耦合的方式很多抖苦,依賴膛腐、關(guān)聯(lián)、組合哲身、聚合等。其中怔揩,我們稱出現(xiàn)成員變量、方法參數(shù)商膊、方法返回值中的類為直接的朋友,而出現(xiàn)在局部變量中的類則不是直接的朋友晕拆。也就是說,陌生的類最好不要作為局部變量的形式出現(xiàn)在類的內(nèi)部吝镣。
這里我們可以用一個現(xiàn)實生活中的例子來講解一下昆庇。比如我們需要一張CD,我們可能去音像店去問老板有沒有我們需要的那張CD末贾,老板說現(xiàn)在沒有整吆,等有的時候你們來拿就行了。在這里我們不需要關(guān)心老板是從哪里表蝙,怎么獲得的那張CD,我們只和老板(直接朋友)溝通昼扛,至于老板從他的朋友那里通過何種條件得到的CD欲诺,我們不關(guān)心抄谐,我們不和老板的朋友(陌生人)進(jìn)行通信扰法,這個就是迪米特的一個應(yīng)用。說白了浦箱,就是一種中介的方式。我們通過老板這個中介來和真正提供CD的人發(fā)生聯(lián)系酷窥。
總結(jié)
到這里伴网,面向?qū)ο蟮牧笤瓌t蓬推,就寫完了澡腾。我們看出來糕珊,這些原則其實都是應(yīng)對不斷改變的需求毅糟。每當(dāng)需求變化的時候,我們利用這些原則來使我們的代碼改動量最小姆另,而且所造成的影響也是最小的。
但是我們在看這些原則的時候迹辐,我們會發(fā)現(xiàn)很多原則并沒有提供一種公式化的結(jié)論,而即使提供了公式化的結(jié)論的原則也只是建議去這樣做。這是因為渺绒,這些設(shè)計原則本來就是從很多實際的代碼中提取出來的,他是一個經(jīng)驗化的結(jié)論宗兼。怎么去用它,用好他染苛,就要依靠設(shè)計者的經(jīng)驗主到。
否則意味著去使用設(shè)計原則可能會使代碼出現(xiàn)過度設(shè)計的情況茶行。大多數(shù)的原則都是通過提取出抽象和接口來實現(xiàn)登钥,如果發(fā)生過度的設(shè)計,就會出現(xiàn)很多抽象類和接口看锉,增加了系統(tǒng)的復(fù)雜度。讓本來很小的項目變得很龐大伯铣,當(dāng)然這也是Java的特性(任何的小項目都會做成中型的項目)轮纫。