向?qū)ο蟮牧笤瓌t
前言
我們都知道面向?qū)ο笥辛笤瓌t,23種設(shè)計(jì)模式。它們可以指導(dǎo)我們?nèi)绾螌?xiě)出更加優(yōu)秀的代碼。六大原則是基礎(chǔ),我們面向?qū)ο缶幊虘?yīng)該盡量遵從這六大原則,才能寫(xiě)出優(yōu)秀的代碼。
23種設(shè)計(jì)模式是前人為我們總結(jié)出的解決某一類(lèi)問(wèn)題的辦法,通過(guò)使用這些模式六大原則基礎(chǔ)之上的。
目錄

六大原則概念
六大原則是面向?qū)ο蟮牧笤瓌t,也就是說(shuō),我們?cè)诰帉?xiě)面向?qū)ο笳Z(yǔ)言的時(shí)候,只有遵守了這六大原則才能寫(xiě)出真正的面向?qū)ο蟆2拍軗碛忻嫦驅(qū)ο蟮乃枷?。我們?xiě)的代碼符合了這六大原則,有助有提高我們代碼的質(zhì)量,寫(xiě)出可擴(kuò)展、可維護(hù)、可讀性好、低耦合、高內(nèi)聚的代碼。
當(dāng)然不符合這些原則,同樣也是可以寫(xiě)代碼的,只不過(guò)寫(xiě)出的代碼質(zhì)量就沒(méi)有保證了,只能說(shuō)勉強(qiáng)算是個(gè)代碼。這好比做人一樣,人也是有原則的,符合了這些原則,那就可以做一個(gè)堂堂正正的好人,不符合這些原則,也能活著。但是當(dāng)不斷的觸犯原則,最后成了一個(gè)沒(méi)有原則的人了,那么結(jié)果顯然可見(jiàn)。如果隨著程序不斷的變大,代碼不斷的沒(méi)有原則,那么最終的結(jié)果就是你的程序無(wú)法進(jìn)行下一步維護(hù)了。
總之我們?cè)趯?xiě)代碼的時(shí)候要盡量符合這些原則!才能寫(xiě)出高質(zhì)量代碼!
單一職責(zé)
單一職責(zé)是我們優(yōu)化代碼的第一步
概念
概念:就一個(gè)類(lèi)而言,應(yīng)該僅有一個(gè)引起它變化的原因
概念可能不太好懂,簡(jiǎn)單來(lái)說(shuō)就是一個(gè)類(lèi)中應(yīng)該是一組相關(guān)性很高的函數(shù)、數(shù)據(jù)的封裝。
下面舉例子:
public?class?Activity{
??//?請(qǐng)求網(wǎng)絡(luò)加載
??public?void?requestNet(){
????String?url?=?editText.getText();
????String?parmas?=?editText.getText();
????//?判斷是否符合某個(gè)條件
????if(xx){
????}
????//?繼續(xù)判斷
????if(xxx){
????}
????....?等等省略1000行
??}
??class?Adapter{
??}
??//?數(shù)據(jù)類(lèi)
??class?Data{
??}
??class?Xxx{
??}
??.....
}
像上面的例子就是一個(gè)很好的反例代表,把所有的職責(zé)全部放到了 Activity 中,把所有的函數(shù)功能都放到了 requestNet 中。這樣勢(shì)必造成 Activity 異常的臃腫,只要一個(gè)職責(zé)發(fā)生變化就能引起 Activity 的變化。比如 Adapter 變化,要去 Activity 中修改,等等都會(huì)對(duì) Activity 造成變化。requestNet 函數(shù)中的功能不夠純粹,里面又包含了很多其他的功能,也會(huì)導(dǎo)致同樣的問(wèn)題。
這也就是概念中提到的,就一個(gè)類(lèi)而言,應(yīng)該僅有一個(gè)引起它變化的原因。
好處
單一職責(zé)的好處很明顯,讓一個(gè)類(lèi)、函數(shù)只負(fù)責(zé)某一項(xiàng)任務(wù)或者功能,可以達(dá)到很好的復(fù)用效果,代碼的可讀性也會(huì)增強(qiáng),可讀性好了,對(duì)應(yīng)的可維護(hù)性也會(huì)增加。
當(dāng)然關(guān)于職責(zé)的劃分是一個(gè)很抽象的概念,每個(gè)人的劃分都會(huì)不同,單一職責(zé)的劃分界限并不總是那么清晰,有的時(shí)候劃分的很細(xì)也會(huì)帶來(lái)不方便。這是一個(gè)靈活掌握的問(wèn)題,關(guān)鍵是設(shè)計(jì)代碼的時(shí)候有沒(méi)有考慮到這種思想。
開(kāi)閉原則
Java 世界里最基礎(chǔ)的設(shè)計(jì)原則,指導(dǎo)我們?nèi)绾谓⒁粋€(gè)穩(wěn)定的、靈活的系統(tǒng)。
概念
軟件中的對(duì)象(類(lèi)、模塊、函數(shù)等)應(yīng)該對(duì)于擴(kuò)展是開(kāi)放的,但是對(duì)于修改是封閉的。
在編寫(xiě)代碼的過(guò)程中,不是一成不變的,需求的變化、升級(jí)、維護(hù)等等都需要對(duì)代碼進(jìn)行修改,修改的時(shí)候就不可避免地將錯(cuò)誤引入原本已經(jīng)測(cè)試過(guò)的舊代碼中,破壞原有系統(tǒng)。因此,當(dāng)軟件需求發(fā)生變化的時(shí)候,我們應(yīng)該優(yōu)先考慮通過(guò)擴(kuò)展的方式來(lái)實(shí)現(xiàn)變化,而不是通過(guò)修改已有代碼來(lái)實(shí)現(xiàn)。
當(dāng)然實(shí)際開(kāi)發(fā)中擴(kuò)展和修改是同時(shí)存在的。應(yīng)該盡量少的去修改代碼,想法去擴(kuò)展代碼。
《面向?qū)ο筌浖?gòu)造》一書(shū)中提到這一原則---開(kāi)閉原則。這一想法認(rèn)為,程序一旦開(kāi)發(fā)完成,程序中的一個(gè)類(lèi)的實(shí)現(xiàn)只應(yīng)該因錯(cuò)誤而被修改,新的或者改變的特性應(yīng)該通過(guò)新建不同的類(lèi)實(shí)現(xiàn),新建的類(lèi)可以通過(guò)繼承的方式來(lái)重用原有類(lèi)。
舉個(gè)簡(jiǎn)單的例子:
public?class?Hello{
??BlackPen?pen?=?new?BlackPen();
??void?writeHello(){
????pen.write("hello?world");
??}
}
//?Pen?類(lèi)可以寫(xiě)出字
public?class?BlackPen{
??public?void?write(String?content){
????System.out.println("content");
??}
}
上面這個(gè)程序中我們可以通過(guò) BlackPen 類(lèi)寫(xiě)出字,有一天需求變了要求寫(xiě)出紅色的字。
public?class?Hello{
??BlackPen?pen?=?new?BlackPen();
??RedPen?redPen?=?new?RedPen();
??void?writeHello(String?flag){
????switch(flag){
????????"XXX":
????????????pen.wiite("hello?world");
????????"YYY":
????????????redPen.write("hello?world")
????}
??}
}
//?BlackPen?類(lèi)可以寫(xiě)出黑字
public?class?BlackPen{
??public?void?write(String?content){
????System.out.println(content);
??}
}
//?RedPen?類(lèi)可以寫(xiě)出紅字
public?class?RedPen{
??public?void?write(String?content){
????System.out.println(content);
??}
}
這樣寫(xiě)通過(guò) switch 來(lái)判斷要調(diào)用那一個(gè),如果繼續(xù)添加其他顏色的筆就繼續(xù)添加。這樣貌似不錯(cuò)。但是試想 Hello 是你提供給別人的一個(gè)框架,那么別人想要繼續(xù)添加可以寫(xiě)出黃色的 Hello Wrold ,是不是就沒(méi)有辦法了,非得讓你去修改 Hello 方法才可以,沒(méi)有了擴(kuò)展性。
現(xiàn)在優(yōu)化成
public?class?Hello{
??Pen?pen?=?new?BlackPen();
??public?void?setPen(Pen?pen){
????this.pen?=?pen;
??}
??void?writeHello(){
?????????pen.write("hello?world")
??}
}
public?interface?Pen{
??write(String?content);
}
//?BlackPen?類(lèi)可以寫(xiě)出黑字
public?class?BlackPen?implement?Pen{
??public?void?write(String?content){
????System.out.println(content);
??}
}
//?RedPen?類(lèi)可以寫(xiě)出紅字
public?class?RedPen?implement?Pen{
??public?void?write(String?content){
????System.out.println(content);
??}
}
這樣就可以擴(kuò)展而不用修改 Hello 內(nèi)的代碼了。
開(kāi)閉原則,對(duì)修改關(guān)閉,對(duì)擴(kuò)展開(kāi)放。并不是說(shuō)完全的不能修改,比如上面內(nèi)容,一開(kāi)始只有一個(gè) BlackPen 的時(shí)候,你沒(méi)有想到擴(kuò)展,可以那樣寫(xiě),但是隨著業(yè)務(wù)變化,出現(xiàn)了不同的 Pen。這個(gè)時(shí)候就需要考慮 Pen 要有可擴(kuò)展性。就不能重復(fù)的在 Hello 類(lèi)中不斷去修改了。而是換一種思路,讓其變得具有可擴(kuò)展。
好處
可以使用我們的程序更加穩(wěn)定,避免修改帶來(lái)的錯(cuò)誤,增加可擴(kuò)展性。當(dāng)一個(gè)類(lèi)中的業(yè)務(wù)不斷的發(fā)生變化需求,不斷的增加業(yè)務(wù)判斷,就需要考慮到擴(kuò)展性了。
里氏替換原則
構(gòu)建擴(kuò)展性更好的系統(tǒng)
概念
所用引用基類(lèi)的地方必須能透明地使用其子類(lèi)的對(duì)象。
只要父類(lèi)能出現(xiàn)的地方,子類(lèi)就可以出現(xiàn),而且替換為子類(lèi)也不會(huì)產(chǎn)生任何錯(cuò)誤或者異常,使用者可能根本就不需要知道是父類(lèi)還是子類(lèi)。
里氏替換原則就是依賴于繼承、多態(tài)這兩大特性。
其實(shí)就是將依賴變成抽象,不依賴于具體的實(shí)現(xiàn)。
比如:
public?class?Window{
??public?void?show(View?child){
????child.draw();
??}
}
public?abstract?class?View{
??public?abstract?void?draw();
??public?void?measure(int?width,int?height){
????//?測(cè)量視圖大小
??}
}
public?class?Button?extends?View{
??public?void?draw(){
????//?繪制按鈕
??}
}
public?class?TextView?extends?View{
??public?void?draw(){
????//?繪制文本
??}
}
Window 是依賴于 View 的,是一個(gè)抽象類(lèi),Window 是依賴于一個(gè)抽象,而不是具體的對(duì)象。這個(gè)時(shí)候傳入任何 View ?的具體對(duì)象都是可以的。
好處
提高擴(kuò)展性,使其不依賴具體的實(shí)現(xiàn),依賴抽象。
依賴倒置原則
讓項(xiàng)目擁有變化的能力
概念
依賴倒置原則指代了一種特定的解耦形式,使得高層次的模塊不依賴于低層的模塊的實(shí)現(xiàn)細(xì)節(jié),依賴模塊被顛倒了。
依賴倒置的關(guān)鍵點(diǎn):
高層模塊不應(yīng)該依賴低層模塊,兩者應(yīng)該依賴其抽象
抽象不應(yīng)該依賴細(xì)節(jié)
細(xì)節(jié)應(yīng)該依賴抽象
在 Java 語(yǔ)言中,抽象就是指接口或抽象類(lèi)。兩者都是不能直接被實(shí)例化的;細(xì)節(jié)就是實(shí)現(xiàn)類(lèi),實(shí)現(xiàn)接口或者繼承抽象類(lèi)而產(chǎn)生的類(lèi)就是細(xì)節(jié)。
高層模塊就是調(diào)用端,低層模塊就是具體的實(shí)現(xiàn)類(lèi)。也就是調(diào)用端不要依賴具體的實(shí)現(xiàn)類(lèi),而是通過(guò)依賴抽象的方式。其實(shí)就是面向接口編程。
其實(shí)和上面里氏替換原則類(lèi)似
好處
降低耦合性,不依賴具體細(xì)節(jié),依賴抽象,提高可擴(kuò)展性
接口隔離原則
系統(tǒng)有更好的靈活性
概念
客戶端不應(yīng)該依賴它不需要的接口。另一種定義:類(lèi)間的依賴關(guān)系應(yīng)該建立在最小的接口上。接口隔離原則將非常龐大、臃腫的接口拆分成更小的和更具體的接口,這樣客戶將會(huì)只需要知道他們感興趣的方法。接口隔離原則的目的是系統(tǒng)解開(kāi)耦合,從而容易重構(gòu)、更改和重新部署。
其實(shí)就是讓一個(gè)接口盡可能的小,方法少,使用戶使用起來(lái)方便。
比如:一個(gè)對(duì)象實(shí)現(xiàn)了多個(gè)接口,有個(gè)接口是關(guān)閉功能,那么當(dāng)這個(gè)對(duì)象想要關(guān)閉的時(shí)候,調(diào)用關(guān)閉方法就可以了,因?yàn)樗鼘?shí)現(xiàn)了多個(gè)接口,有多個(gè)方法,調(diào)用的時(shí)候就暴露了其他接口函數(shù)。這個(gè)時(shí)候我們僅需要它暴露關(guān)閉的接口就可以了,隱藏其他接口信息。
好處
使用起來(lái)更加方便靈活
迪米特原則
概念
迪米特原則也稱(chēng)為最少知道原則。一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象有最少的了解。通俗地講,一個(gè)類(lèi)應(yīng)該對(duì)需要耦合或調(diào)用的類(lèi)知道得最少,類(lèi)的內(nèi)部如何實(shí)現(xiàn)與調(diào)用者或者依賴者沒(méi)有關(guān)系,調(diào)用者或者依賴者只需要知道它的需要的方法就可以了,其他的可一概不管。類(lèi)與類(lèi)之間關(guān)系越密切,耦合度越大,當(dāng)一個(gè)類(lèi)發(fā)生改變時(shí),對(duì)另一個(gè)類(lèi)的影響也越大。
迪米特原則還可以解釋為:只與直接朋友通信。
也就是說(shuō),應(yīng)該盡可能少的與別的朋友通信,僅與最直接的朋友通信。
兩個(gè)對(duì)象成為朋友的方式有多種:組合、聚合、依賴等等。
好處
降低依賴、使用簡(jiǎn)單
總結(jié)
這六大原則不是相互獨(dú)立的,而是互相融合,你中有我,我中有你。
單一職責(zé)告訴我們要盡量的分離代碼,不斷的精分代碼,不同的模塊實(shí)現(xiàn)不同的功能。這樣不會(huì)所有功能都融合在一塊,方便閱讀、維護(hù)代碼。
開(kāi)閉原則、里氏替換原則、依賴倒置原則:本質(zhì)上都是通過(guò)抽象來(lái)提高擴(kuò)展性。不依賴具體的實(shí)現(xiàn)而依賴抽象,就會(huì)增加很多擴(kuò)展性,抽象可以有需要不同的實(shí)現(xiàn)。
接口隔離原則:和單一職責(zé)有類(lèi)似,就是通過(guò)接口細(xì)分化,暴露最少的方法。要想有某個(gè)功能,只需要實(shí)現(xiàn)這個(gè)接口就可以了,與其他接口無(wú)關(guān)。
迪米特原則:盡量依賴更少的類(lèi),盡量對(duì)外界暴露更少的方法
實(shí)現(xiàn)這六大原則主要是通過(guò)面向接口編程,面向抽象編程。不依賴具體的實(shí)現(xiàn)。每個(gè)類(lèi)都有一個(gè)抽象(抽象類(lèi)、接口)。當(dāng)高層級(jí)模塊需要依賴這個(gè)類(lèi)的時(shí)候,依賴它的抽象,而不是具體。這個(gè)時(shí)候就可以靈活的改變其實(shí)現(xiàn)了。
