更好的代碼
我總在思考如何讓自己寫成更優(yōu)雅的代碼,如何寫出更易維護(hù)诵姜,更易讀懂的代碼汽煮,我覺得很幸運(yùn),第一份實(shí)習(xí)工作棚唆,在很大程度上幫助了我暇赤,但后來(lái)其實(shí)我發(fā)現(xiàn)我學(xué)到只是一些皮毛,還有更多需要學(xué)習(xí)的宵凌,所以為了寫出更簡(jiǎn)潔鞋囊,更易他人讀懂的代碼,我開始接觸兩本書瞎惫,一本書現(xiàn)在這里寫讀書筆記的這本書溜腐,另一本是很著名的代碼整潔之道,那么我們來(lái)介紹一下這兩本書微饥。
Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)這本書主要講了面向?qū)ο?下面統(tǒng)稱OOP)的六大原則逗扒,23種設(shè)計(jì)模式以及MVC和MVP這兩種應(yīng)用架構(gòu)去看待應(yīng)用開發(fā)。
當(dāng)我們開始了解并遵守OOP的六大原則時(shí)欠橘,我們可以寫出更易擴(kuò)展更簡(jiǎn)潔的代碼矩肩,并了解到為什么這樣做更好,其實(shí)Java在面向?qū)ο缶幊痰那疤嵯滤嘈覀儜?yīng)該更多的是面向抽象編程黍檩,而這里抽象既包括了抽象類也包括了接口。
23種設(shè)計(jì)模式可以讓我們了解到如何更好的更簡(jiǎn)單的解決一個(gè)問題始锚,但是有時(shí)候大家卻在濫用設(shè)計(jì)模式刽酱,所以如何平衡使用也是需要學(xué)習(xí)的。
而最后講的MVC和MVP我們也應(yīng)該更多的了解瞧捌,Android本身就采用了MVC模式棵里,而MVP模式這兩年也被運(yùn)用的比較廣泛,以至于官方都推出一個(gè)MVP的DEMO姐呐,從而統(tǒng)一大家的書寫習(xí)慣和對(duì)MVP的理解殿怜。
代碼整潔之道這本書其實(shí)我還沒開始看,但之前在別的公司讀過一部分曙砂,看到一句話头谜,讓我印象深刻,第一章作者用每個(gè)人角度討論了什么是更好的代碼鸠澈,讓我印象深刻那句話是這樣的——什么的代碼是最好的柱告,就是每當(dāng)我覺得代碼可以進(jìn)行修改和完善的時(shí)候截驮,我最后總是回到起點(diǎn)。
我非常認(rèn)同寫代碼就像寫文章這個(gè)觀點(diǎn)际度,更多時(shí)候我們不是寫給自己看的葵袭,所以可以做到讓其他人一眼就能看懂你在講什么,實(shí)現(xiàn)了什么功能才是我們需要做的甲脏,這就涉及到很多要做好的事情眶熬,更好的命名,更簡(jiǎn)潔的函數(shù)块请,職責(zé)單一的類娜氏,易擴(kuò)展的設(shè)計(jì)等等。
OOP六大原則
作者在講述的時(shí)候很生動(dòng)墩新,舉一系列例子贸弥,使得我們更好地理解它講述出來(lái)的六大原則,那我么下面我們就來(lái)看看OOP的六大原則到底如何運(yùn)用和他們起到的作用海渊。
1.單一職責(zé)原則
定義:就一類而言绵疲,應(yīng)該僅有一個(gè)引起它變化的原因。
作者給了另一個(gè)概括臣疑,一個(gè)類中應(yīng)該是一組相關(guān)性很高的函數(shù)盔憨、數(shù)據(jù)的封裝,也就是說一個(gè)類它只做一件事讯沈,我并不想成為一個(gè)神一樣的類(什么功能都有什么都可以做)郁岩,而只是我負(fù)責(zé)一件事,我把它做的很好缺狠,所以我們首先要思考的是我寫這個(gè)類他的職責(zé)是什么问慎?這有時(shí)候總是不能清晰的界定,導(dǎo)致了我們寫出類并沒有很好遵守這個(gè)原則挤茄。
作者在舉例的時(shí)候講了一個(gè)很實(shí)在的例子如叼,讓一個(gè)新手去實(shí)現(xiàn)一個(gè)ImageLoader,而他很快就寫好了穷劈,交給主管去看笼恰,主管卻不是很滿意,因?yàn)樗言趫D片加載的類中同時(shí)做了兩件事情歇终,一個(gè)是加載的圖片社证,一個(gè)是對(duì)圖片緩存,而主管看到后表示了不滿练湿,希望他可以把代碼拆分猴仑,把各個(gè)功能獨(dú)立出來(lái)审轮,所以在修改后他就把圖片的加載和圖片的緩存分成了兩個(gè)不同的類肥哎,讓它們只負(fù)責(zé)一件事辽俗。
而這例子很好的解釋了什么是單一職責(zé)原則,一個(gè)多功能的類篡诽,拆分成若干個(gè)單一功能的類崖飘,從而降低了代碼耦合。
2.開閉原則
定義:軟件中的對(duì)象(類杈女、模塊朱浴、函數(shù)等)應(yīng)該對(duì)擴(kuò)展開放,但是达椰,對(duì)于修改應(yīng)該是封閉的翰蠢。
作者講到我們?cè)陂_發(fā)過程中,需求總是會(huì)有變化啰劲,所以代碼也就免不了進(jìn)行相應(yīng)的改變梁沧,而這條原則告訴我們盡量不是通過修改原有代碼來(lái)實(shí)現(xiàn)新需求而是對(duì)現(xiàn)有功能進(jìn)行擴(kuò)展來(lái)應(yīng)對(duì)需求的變化。我們說到擴(kuò)展可能會(huì)想要繼承蝇裤,實(shí)現(xiàn)廷支,加入新的類,理想的情況是只通過這這幾種方式來(lái)完成栓辜,但實(shí)際情況可能是這幾方式的同時(shí)也伴隨著對(duì)原有代碼的的修改恋拍。
作者在書中還引用了《面向?qū)ο筌浖?gòu)造》作者的話,因?yàn)槭撬岢隽碎_閉原則藕甩,——程序一旦開發(fā)完成施敢,程序中一個(gè)類(原有代碼)的實(shí)現(xiàn)只應(yīng)該因?yàn)殄e(cuò)誤而被修改,新的或者改變的特性應(yīng)該通過新建不同的類實(shí)現(xiàn)辛萍,新建的類可以通過繼承的方式來(lái)重用原有的代碼(可以看出提出開閉原則的人提倡實(shí)現(xiàn)繼承)悯姊。
作者在上一個(gè)例子的基礎(chǔ)上舉了一個(gè)例子,那個(gè)新手已經(jīng)完成了圖片加載和圖片緩存的拆分贩毕,但隨著用戶的增多悯许,有些問題也就暴露了出來(lái),它之前只是使用了內(nèi)存緩存辉阶,所以當(dāng)內(nèi)存緩存的內(nèi)容被清理時(shí)先壕,用戶就只能再次下載圖片,就導(dǎo)致了圖片加載緩慢和消耗用戶流量谆甜,此時(shí)應(yīng)該考慮把圖片緩存到本地垃僚,這樣就解決了內(nèi)存緩存被清理的問題,不必再去重新下載圖片规辱。
而他只是添加一個(gè)新的類谆棺,然后在圖片緩存類里面加入使用,通過判斷使用哪種方式緩存罕袋,此時(shí)主管又給出意見改淑,用戶可以自己決定使用哪種緩存碍岔,單獨(dú)使用某一個(gè)或者同時(shí)使用兩種緩存。而其實(shí)最好的緩存策略其實(shí)是同時(shí)使用朵夏,但是優(yōu)先使用內(nèi)存緩存的蔼啦,如果被清理了再去查看本地緩存是否存在。
此時(shí)他已經(jīng)寫了三個(gè)關(guān)于內(nèi)存緩存的類了仰猖,內(nèi)存緩存捏肢,本地緩存,雙緩存饥侵,代碼如下鸵赫,主管看到代碼告訴了他不能每次加入新緩存你都去修改原有代碼,這樣很容易引入Bug躏升,且會(huì)使得邏輯越來(lái)越復(fù)雜奉瘤,因?yàn)橐ㄟ^條件判斷到底需要使用哪種緩存,主管給他講解了開閉原則煮甥,但是作為新手他并不理解盗温,有點(diǎn)云里霧里的,不知如何下手成肘,所以主管親自修改了代碼卖局。
// 新手的代碼
public class ImageLoader {
// 內(nèi)存緩存
ImageCache mImageCache = new ImageCache();
// 本地緩存
DiskCache mDiskCache = new DiskCache();
// 雙緩存
DoubleCache mDoubleCache = new DoubleCache();
// ....省略下面代碼
}
// 主管的代碼
// 首先提取出了一個(gè)圖片緩存接口
public interface ImageCache {
void put(String url, Bitmap bitmap);
Bitmap get(String url);
}
// 然后分別實(shí)現(xiàn)了MemoryCache,DiskCache双霍,DoubleCache砚偶,代碼省略
public class ImageLoader {
// 圖片緩存,并設(shè)置了內(nèi)存緩存為默認(rèn)方式
private ImageCache mImageCache = new MemoryCache();
// 注入緩存實(shí)現(xiàn) 利用了向上轉(zhuǎn)型
public void setImageCache(ImageCache imageCache) {
mImageCache = imageCache;
}
// 省略其他成員變量和方法
}
// 使用方法 只是通過傳入不同實(shí)現(xiàn)就可以切換緩存方式
ImageLoader loader = new ImageLoader();
loader.setImageCache(new MemoryCache());
loader.setImageCache(new DiskCache());
loader.setImageCache(new DoubleCache());
我們看得到通過setImageCache(ImageCache imageCache) 方式注入不同的緩存實(shí)現(xiàn)洒闸,使得ImageLoader代碼變得更簡(jiǎn)單染坯,健壯,提升高了它的靈活性和可擴(kuò)展性丘逸,如果還有還有新的緩存方式单鹿,只需要去實(shí)現(xiàn)ImageCachej接口就可以使用了。
所以當(dāng)需求發(fā)生變化時(shí)深纲,應(yīng)該盡量通過擴(kuò)展的方式來(lái)實(shí)現(xiàn)變化仲锄,而不是通過修改已有代碼來(lái)實(shí)現(xiàn),但要做到開閉原則湃鹊,首先我們應(yīng)該先寫出更易擴(kuò)展的代碼儒喊。
3.里氏替換原則
定義:所有引用的基類的地方都必須能透明的使用其子類的對(duì)象。
作者用了一句很通俗的話講解了這個(gè)原則——只要父類能出現(xiàn)打的地方之類就可以出現(xiàn)币呵。
就像開閉原則中舉的例子怀愧,主管修改了代碼,創(chuàng)建了一個(gè)ImageCache,而其他緩存類都是他的實(shí)現(xiàn)類芯义,而setImageCache(ImageCache imageCache) 需要的就是ImageCache類型肛搬,這時(shí)候我們就可以使用MemoryCache,DiskCache毕贼,DoubleCache來(lái)替換ImageCache的工作。ImageCache確定了規(guī)范蛤奢,而新的緩存需求都可以通過實(shí)現(xiàn)它然后替換ImageCache來(lái)工作鬼癣,從而保證了可擴(kuò)展性。
故里氏替換原則就是通過建立抽象啤贩,建立規(guī)范待秃,然后在運(yùn)行時(shí)通過具體實(shí)現(xiàn)來(lái)替換掉抽象,從而保證了系統(tǒng)的擴(kuò)展性和靈活性痹屹≌掠簦可見,在開發(fā)過程中運(yùn)用抽象是走向代碼優(yōu)化的重要一步志衍。
開閉原則和里氏替換原則往往都是一同出現(xiàn)的暖庄,通過里氏替換原則達(dá)到對(duì)擴(kuò)展的開發(fā),對(duì)修改關(guān)閉的效果楼肪。
4.依賴倒置原則
定義:指代了一種特定形式的解耦形式培廓,使得高層次的模塊不依賴于低層次的模塊的實(shí)現(xiàn)細(xì)節(jié)的目的,依賴模塊被顛倒了春叫。
依賴倒置原則的三個(gè)關(guān)鍵點(diǎn):
(1).高層次模塊不應(yīng)該依賴于底層模塊肩钠,兩者都應(yīng)該依賴其抽象;
(2).抽象不應(yīng)依賴細(xì)節(jié)暂殖;
(3).細(xì)節(jié)應(yīng)該依賴抽象价匠。
接下來(lái)作者解釋了一些概念:抽象就是指接口或者抽象類;細(xì)節(jié)就是實(shí)現(xiàn)類呛每;高層模塊就是調(diào)用端踩窖,低層模塊就是具體實(shí)現(xiàn)類。
所以作者說依賴倒置原則在Java中表現(xiàn)就是:模塊間依賴是通過抽象發(fā)生的晨横,實(shí)現(xiàn)類之間并不產(chǎn)生直接依賴關(guān)系毙石,其依賴關(guān)系是通過接口或抽象類產(chǎn)生的。
一句話概括:面向接口編程颓遏,或者說面向抽象編程徐矩。
我們依然可以通過上面的例子繼續(xù)說明,代碼如下:
// 如果在ImageLoader中直接這樣寫的話
// 就是直接依賴于細(xì)節(jié)(直接依賴實(shí)現(xiàn)類)
private DoubleCache mImageCache = new DoubleCache();
// 而主管的代碼卻直接完成1.2.3.4這是個(gè)原則
// 依賴于抽象叁幢,通過向上轉(zhuǎn)型滤灯,有一個(gè)默認(rèn)的實(shí)現(xiàn)類
private ImageCache mImageCache = new MemoryCache();
// 設(shè)置緩存策略,依賴于抽象
public void setImageCache(ImageCache imageCache) {
mImageCache = imageCache;
}
依賴于抽象,依賴于基類鳞骤,這樣當(dāng)需求發(fā)生變化窒百,只需要實(shí)現(xiàn)ImageCache或者繼承已實(shí)現(xiàn)的之類都可以完成緩存功能,然后將實(shí)現(xiàn)注入到setImageCache(ImageCache imageCache)就可以了豫尽。
5.接口隔離原則
定義:客戶端不應(yīng)該依賴它不需要的接口篙梢。或者說類的依賴關(guān)系應(yīng)該將在最小的接口上美旧。
作者說接口隔離的目的是系統(tǒng)接口耦合渤滞,從而容易重構(gòu)、更改和重新部署榴嗅。一句話:讓客戶端以來(lái)的接口盡可能小妄呕。
作者舉了一個(gè)例子,當(dāng)我們?cè)谑褂昧鞯臅r(shí)候我們需要在finally中判斷是否為空嗽测,如果不為空需要close()它绪励,但每次使用流,都這么寫唠粥,也會(huì)讓代碼變得不優(yōu)美疏魏,這個(gè)時(shí)候我們考慮借助外力,就比如Java為我們提供了一個(gè)Closeable接口晤愧,而它有100多個(gè)實(shí)現(xiàn)類蠢护,所以那些類都可以使用它,代碼如下:
// 這就是修改之前的代碼 try/catch中還有try/catch
FileOutputStream fileOutputStream = null;
try {
// 邏輯省略
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 寫了個(gè)CloseUtil類养涮,然后西面提供這個(gè)靜態(tài)方法葵硕,所有實(shí)現(xiàn)了Closeable的類都可以調(diào)用這個(gè)方法
public static void closeQuietly (Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 我們只需要在finally中調(diào)用這一句話就好了
CloseUtil.closeQuietly(xxx);
不僅讓代碼的可讀性增加了,還保證了它的重用性贯吓,這里也用到了依賴倒置原則懈凹,closeQuietly()方法的參數(shù)就一個(gè)抽象,做到了我只需要知道這個(gè)對(duì)象是可關(guān)閉的悄谐,其他一概不管辛介评,也就是作者所說的接口隔離原則。
6.迪米特原則
定義:一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象有最少的了解爬舰。
通俗的講们陆,一個(gè)類應(yīng)該對(duì)自己需要耦合或者調(diào)用的類知道的最少,類的內(nèi)部如何實(shí)現(xiàn)與調(diào)用者或者依賴者沒有關(guān)系情屹,只需要知道它需要的方法即可坪仇,其他的一概不管,類與類之間的關(guān)系越密切垃你,耦合度也就越大椅文。
迪米特原則還有一個(gè)英文解釋:Only talk to your immediate friends.翻譯過來(lái)也就是說之與直接朋友進(jìn)行通信喂很。
作者舉了例子方便我們理解,現(xiàn)在有三個(gè)類皆刺,一個(gè)是房間類少辣,中介類以及租客類,租過房子的朋友都應(yīng)該知道羡蛾,中介手里是有很多是房子的漓帅,而我們想要找什么樣的房子只要告訴中介條件,他就會(huì)幫你找到合適的房子痴怨,這里也是忙干,在租客類眼中他的直接朋友就是中介類,我所有的找房子腿箩,打掃房間,修電器劣摇,交水電費(fèi)都找他一個(gè)類就可以珠移,因?yàn)樗麜?huì)幫我們都搞定,至于房東長(zhǎng)什么樣子末融,房產(chǎn)證放在哪里了租客就都不需要關(guān)心了钧惧,這樣就形成了租客只和中介打交道,而中介管理者房屋列表勾习,這也就是迪米特原則浓瞪。
上面就是OOP的六大原則,我講的還是太啰嗦了巧婶,希望通過更多的寫作乾颁,可以改善自己的表達(dá),也希望可以把自己學(xué)到的知識(shí)通過自己表述講給他講聽艺栈。Android源碼設(shè)計(jì)模式卻是一本好書英岭,希望大家也可以讀一讀。