本文由 玉剛說(shuō)寫作平臺(tái) 提供寫作贊助
原作者:卻把清梅嗅
原文地址:https://mp.weixin.qq.com/s/T6ZCQRydzFgVUezlywC8Zw
版權(quán)聲明:本文版權(quán)歸微信公眾號(hào) 玉剛說(shuō) 所有园匹,未經(jīng)許可钉鸯,不得以任何形式轉(zhuǎn)載!
術(shù)與道
數(shù)據(jù)結(jié)構(gòu)音同,算法千扶,設(shè)計(jì)模式被認(rèn)為是程序員必備技能的三叉戟料祠,如果說(shuō)編程語(yǔ)言的語(yǔ)法特性和業(yè)務(wù)編碼能力是【術(shù)】,那么這三者可以稱得上是【道】——【術(shù)】可以讓你在IT行業(yè)賴以生存澎羞,而【道】則決定你未來(lái)在技術(shù)這條道路上可以走多遠(yuǎn)髓绽。
邊學(xué)邊忘的窘境
先自我介紹一下。
我是一個(gè)兩年工作經(jīng)驗(yàn)的Android開發(fā)人員妆绞,就像很多同行一樣顺呕,對(duì)于數(shù)據(jù)結(jié)構(gòu),算法括饶,設(shè)計(jì)模式這些被奉為程序員必修的三門內(nèi)功株茶,幾乎沒有去系統(tǒng)性地學(xué)習(xí)過(曾經(jīng)專業(yè)課的數(shù)據(jù)結(jié)構(gòu),如今也基本還給了老師)图焰。
你問我想不想當(dāng)一個(gè)稱職的程序員启盛,當(dāng)然!數(shù)據(jù)結(jié)構(gòu)技羔,算法以及設(shè)計(jì)模式不止一次的被我列入學(xué)習(xí)計(jì)劃中僵闯,我認(rèn)為這是有意義并且必須的,并且藤滥,我也嘗試在閑暇之余去主動(dòng)學(xué)習(xí)它們棍厂。
但是很遺憾,至今為止超陆,有關(guān)于數(shù)據(jù)結(jié)構(gòu)和算法,我依然處于入門階段浦马,原因就是:
我學(xué)會(huì)沒多久时呀,一直沒機(jī)會(huì)用到,然后就忘了晶默!
如果您翻看過我之前的 相關(guān)博客 或者我 Github 的 這個(gè)倉(cāng)庫(kù)谨娜,就能發(fā)現(xiàn),我曾經(jīng)關(guān)于數(shù)據(jù)結(jié)構(gòu)和算法磺陡,都做出過嘗試趴梢,但是遺憾的是漠畜,我并不能學(xué)以致用 ——它們匆匆而來(lái),匆匆而去坞靶,就好像生命中的過客一樣憔狞。
至于設(shè)計(jì)模式,因?yàn)榭臻e時(shí)間學(xué)習(xí)的時(shí)間并不多彰阴,雖然我也經(jīng)常通過博客或者書籍學(xué)習(xí)設(shè)計(jì)模式瘾敢,但是結(jié)果依然沒有太大的改變:過于抽象的概念,加上不經(jīng)常使用尿这,讓我很快把這些概念忘得差不多了簇抵。
我覺得這種學(xué)習(xí)方式效率不是很高,于是我決定換一種學(xué)習(xí)的方式——通過閱讀Github上那些開源庫(kù)的源碼射众,學(xué)習(xí)其中的設(shè)計(jì)思想和理念碟摆。
動(dòng)機(jī)
和大多數(shù)人一樣,我只是在編程行業(yè)眾多平凡的開發(fā)者中的一員叨橱,在我職業(yè)生涯的伊始典蜕,我沒有接觸過技術(shù)大牛, 但是閱讀源碼可以讓我零距離碰撞全球行業(yè)內(nèi)最頂尖工程師們的思想,我認(rèn)為對(duì)我而言這是一種效果不錯(cuò)的學(xué)習(xí)方式雏逾,讓我受益匪淺嘉裤。
事實(shí)上,在要寫這篇博客的時(shí)候栖博,我依然忐忑不安屑宠,我擔(dān)心一篇文章如果寫的不夠好,會(huì)給讀者帶來(lái)誤導(dǎo)仇让。
我最終鼓起勇氣寫這篇文章的目的是:我想通過分享個(gè)人對(duì)于設(shè)計(jì)模式的理解典奉,以及自己的學(xué)習(xí)方式和所得,這種學(xué)習(xí)方式可能并非適用于所有人丧叽,但是它至少能給予需要的人一個(gè)參考卫玖。
設(shè)計(jì)模式的分類
我們先看設(shè)計(jì)模式的分類:
范圍 | 創(chuàng)建型 | 結(jié)構(gòu)型 | 行為型 |
---|---|---|---|
類 | Factory Method(工廠方法) | Adapter(類) (適配器) | Interpreter(解釋器) Template Method(模版方法) |
對(duì)象 | Abstract Factory(抽象工廠) Builder(建造者) Prototype(原型) Singleton(單例) |
Bridge(橋接) Composite(組合) Decorator(裝飾者) Fa?ade(外觀) Flyweight(享元) Proxy(代理) |
Chain of Responsibility(職責(zé)鏈) Command(命令) Iterator(迭代器) Mediator(中介者) Memento(備忘錄) Observer(觀察者) State(狀體) Strategy(策略) Visitor(訪問者) |
這是我從 這篇文章 中找到的對(duì)設(shè)計(jì)模式的歸納。
同時(shí)踊淳,我們需要了解到假瞬,設(shè)計(jì)模式的6個(gè)基本原則(這里先列出來(lái),接下來(lái)會(huì)參考案例一個(gè)個(gè)解釋):
- 1迂尝、單一職責(zé)原則(Single Responsibility Principle)
- 2脱茉、里氏代換原則(Liskov Substitution Principle)
- 3、依賴倒轉(zhuǎn)原則(Dependence Inversion Principle)
- 4垄开、接口隔離原則(Interface Segregation Principle)
- 5琴许、迪米特法則,又稱最少知道原則(Demeter Principle)
- 6溉躲、開閉原則(Open Close Principle)
在設(shè)計(jì)模式的學(xué)習(xí)過程中榜田,這些設(shè)計(jì)模式并非是按照不同類型循序漸進(jìn)講解的益兄,更多的場(chǎng)景是,多個(gè)不同類型的設(shè)計(jì)模式相互組合——最終展示出來(lái)的是一個(gè)完整的架構(gòu)設(shè)計(jì)體系箭券。這種設(shè)計(jì)模式復(fù)雜組合帶來(lái)的好處是:高內(nèi)聚净捅,低耦合,這使得庫(kù)本身的拓展非常簡(jiǎn)單邦鲫,同時(shí)也非常便于單元測(cè)試灸叼。
當(dāng)然,對(duì)于通過源碼庆捺,想一窺設(shè)計(jì)思想的學(xué)習(xí)者來(lái)說(shuō)古今,額外的接口,以及可能隨之額來(lái)額外的代碼會(huì)需要更多的學(xué)習(xí)成本滔以,對(duì)于最初的我來(lái)說(shuō)捉腥,復(fù)雜的設(shè)計(jì)真的給我?guī)?lái)了很大的困擾,我試圖去理解和反思這樣設(shè)計(jì)的好處——它的確花費(fèi)了我更多的時(shí)間你画,但是更讓我受益匪淺抵碟。
最初的收獲——?jiǎng)?chuàng)建型模式
在Android學(xué)習(xí)的過程中,我最先接觸到的就是創(chuàng)建型模式坏匪,所謂創(chuàng)建型模式拟逮,自然與對(duì)象的創(chuàng)建有關(guān)。
實(shí)際上适滓,不談源碼敦迄,實(shí)際開發(fā)中,我們也遇到了很多創(chuàng)建型模式的體現(xiàn)凭迹,最常見的當(dāng)屬單例模式和建造者模式(Builder)罚屋。
1.“最簡(jiǎn)單”的設(shè)計(jì)模式
我們以單例模式為例,他的定義是:
“一個(gè)類有且僅有一個(gè)實(shí)例嗅绸,并且自行實(shí)例化向整個(gè)系統(tǒng)提供脾猛。”
相信大家對(duì)這個(gè)單例模式并不陌生鱼鸠,它被稱為 “設(shè)計(jì)模式中最簡(jiǎn)單的形式之一”猛拴,它很簡(jiǎn)單,并且易于理解蚀狰,開發(fā)者總能遇到需要持有唯一對(duì)象的業(yè)務(wù)需求漆弄。
以Android開發(fā)為例,經(jīng)常需要在某個(gè)類中造锅,使用到Application對(duì)象,它本身是唯一的廉邑,因此我們只需要通過一個(gè)類持有它的靜態(tài)引用哥蔚,然后通過靜態(tài)方法獲取就可以了倒谷。
另外的一種需求是,某個(gè)類的對(duì)象會(huì)占用很大的內(nèi)存糙箍,我們也沒有必要對(duì)這個(gè)類實(shí)例化兩次渤愁,這樣,保持其對(duì)象的類單例深夯,能夠省下更多的性能空間抖格,比如Android的數(shù)據(jù)庫(kù)db的引用。
實(shí)際上咕晋,單例模式的細(xì)分下來(lái)雹拄,有很多種實(shí)現(xiàn)方式,比如眾所周知的懶漢式掌呜,餓漢式滓玖,Double CheckLock,靜態(tài)內(nèi)部類质蕉,枚舉势篡,這些不同的單例實(shí)現(xiàn)方式,都有各自的優(yōu)缺點(diǎn)(比如是否線程安全)模暗,也對(duì)應(yīng)著不同的適用場(chǎng)景禁悠,這也正是單例模式作為看起來(lái)“最簡(jiǎn)單”同時(shí)也是面試中的重點(diǎn)考察項(xiàng)目的原因。
這些不同的實(shí)現(xiàn)方式兑宇,百度上講解的非常詳細(xì)碍侦,本文不贅述。
我們需要理解的是顾孽,我們什么時(shí)候使用單例模式祝钢。
對(duì)于系統(tǒng)中的某些類來(lái)說(shuō),只有一個(gè)實(shí)例很重要若厚,比如上述的Application拦英,這很好理解,實(shí)際上测秸,在開發(fā)過程中疤估,我們更需要關(guān)注一些細(xì)節(jié)的實(shí)現(xiàn)。
比如對(duì)Gson的單例霎冯。
實(shí)際開發(fā)中铃拇,調(diào)用Gson對(duì)象進(jìn)行轉(zhuǎn)換的地方非常多,如果在調(diào)用的地方每次new Gson的話沈撞,是影響性能的慷荔。
Gson本身是線程安全的,它可以被多個(gè)線程同時(shí)使用缠俺,因此显晶,我更傾向于通過下面的方式獲取Gson的實(shí)例:
public class Gsons {
private static class Holder {
private static final Gson INSTANCE = new Gson();
}
public static Gson getInstance() {
return Holder.INSTANCE;
}
}
不僅是Gson, 除此之外還有比如網(wǎng)絡(luò)請(qǐng)求的相關(guān)管理類(Retrofit對(duì)象贷岸,ServiceManager等),Android系統(tǒng)提供的各種XXXManager(NotificationManager)等等磷雇,這些通過單例的方式去管理它偿警,能夠讓你業(yè)務(wù)設(shè)計(jì)的更加嚴(yán)謹(jǐn)。
2.Builder的鏈?zhǔn)秸{(diào)用
建造者模式(Builder Pattern)使用多個(gè)簡(jiǎn)單的對(duì)象一步一步構(gòu)建成一個(gè)復(fù)雜的對(duì)象唯笙。
Android開發(fā)者一定很熟悉它螟蒸,因?yàn)槲覀儎?chuàng)建AlertDialog的時(shí)候,鏈?zhǔn)秸{(diào)用的API實(shí)在是賞心悅目:
new AlertDialog
.Builder(this)
.setTitle("標(biāo)題")
.setMessage("內(nèi)容")
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//...
}
})
.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//....
}
})
.create()
.show();
此外崩掘,稍微細(xì)心的同學(xué)會(huì)發(fā)現(xiàn)七嫌,其實(shí)JDK中,StringBuilder和StringBuffer的源碼中的append()方法也是Builder模式的體現(xiàn):
public StringBuilder append(String str) {
super.append(str); // 調(diào)用基類的append方法
return this;
}
// 基類的append方法
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this; // 返回構(gòu)建對(duì)象
}
除了賞心悅目的代碼之外呢堰,我更關(guān)注Builder模式的使用場(chǎng)景:
當(dāng)我們面臨著一個(gè)復(fù)雜對(duì)象的創(chuàng)建工作抄瑟,其通常由各個(gè)部分的子對(duì)象用一定的算法構(gòu)成;由于需求的變化枉疼,這個(gè)復(fù)雜對(duì)象的各個(gè)部分經(jīng)常面臨著劇烈的變化皮假,但是將它們組合在一起的算法卻相對(duì)穩(wěn)定。
很好骂维,我把 這個(gè)學(xué)習(xí)網(wǎng)站 關(guān)于Builder模式的適用場(chǎng)景復(fù)制下了下來(lái)惹资,我曾經(jīng)在學(xué)習(xí)它的時(shí)候嘗試去理解它的敘述,所得出的結(jié)論是 ——上文的定義非常嚴(yán)謹(jǐn)航闺,但是我看不懂褪测。
我們參考AlertDialog,對(duì)于一個(gè)Dialog而言潦刃,它的基本構(gòu)成是復(fù)雜的(有標(biāo)題侮措,內(nèi)容,按鈕及其對(duì)應(yīng)的事件等等屬性)乖杠,但是在實(shí)際需求中分扎,不同的界面,我們需要展示給用戶的Dialog是不一樣的(標(biāo)題不一樣胧洒,內(nèi)容不一樣畏吓,點(diǎn)擊事件也不一樣),這些各個(gè)部分都是在不斷劇烈的變化卫漫,但是他們組合起來(lái)是相對(duì)穩(wěn)定的(就是一個(gè)Dialog彈出展示在界面上)菲饼。
在這種情況下,我們可以嘗試使用Builder模式列赎,和普通的構(gòu)造器生成對(duì)象不同宏悦,如果沒有需求,我們可以忽略配置某些屬性——對(duì)于Dialog,我可以不去定義title饼煞,也可以不去定義取消按鈕的點(diǎn)擊事件辫塌,他們內(nèi)部都有默認(rèn)的處理;此外派哲,對(duì)于API的設(shè)計(jì)來(lái)講,Builder模式更利于去擴(kuò)展新的功能或者屬性掺喻。
Builder模式在我們開發(fā)中非常常見芭届,除上述案例之外,Android流行的圖片加載庫(kù)感耙,以及Notification通知的實(shí)例化等等褂乍,都能看到Builder的身影。
上文說(shuō)到即硼,Builder模式對(duì)于對(duì)象的創(chuàng)建提供了非常賞心悅目的API逃片,我理解了Builder模式的思想和實(shí)現(xiàn)方式之后,便嘗試給自己的一些工具類加一些這樣的設(shè)計(jì)只酥。
很快褥实,我遇到了一個(gè)問題,那就是——這樣寫太TM累了裂允!
3.避免過度設(shè)計(jì)
關(guān)于過度設(shè)計(jì)的定義损离,請(qǐng)參考 什么是軟件開發(fā)中的過度設(shè)計(jì)? 的解釋绝编,我認(rèn)為講解的非常風(fēng)趣且易懂僻澎。
從我個(gè)人的角度而言,我遇到了問題十饥,我嘗試給一些工具改為Builder實(shí)現(xiàn)窟勃,結(jié)果是,我添加了很多很多代碼逗堵,但是效果平平秉氧。
不僅如此,這樣的設(shè)計(jì)給我的工具帶來(lái)了更多的復(fù)雜度砸捏,本來(lái)一個(gè)構(gòu)造器new一下能解決的問題谬运,非要很多行代碼鏈?zhǔn)脚渲茫@種設(shè)計(jì)垦藏,做了還不如不做梆暖。
這樣的結(jié)果,讓我對(duì)網(wǎng)絡(luò)上一位前輩的總結(jié)非常贊同掂骏,那就是:
設(shè)計(jì)模式的一個(gè)重要的作用是代碼復(fù)用轰驳,最終的目的是提升效率。
所以,一個(gè)模式是否適合或必要级解,只要看看它是否能減少我們的工作冒黑,提升我們的工作效率。
那么勤哗,如何避免過度設(shè)計(jì)抡爹,我的經(jīng)驗(yàn)告訴我,寫代碼之前多思考芒划,考慮不同實(shí)現(xiàn)方式所需的成本冬竟,保證代碼的不斷迭代和調(diào)整。
即使如此民逼,在開發(fā)的過程中泵殴,過度設(shè)計(jì)仍然是難以避免的情況,只有依靠經(jīng)驗(yàn)的積累和不斷的總結(jié)思考拼苍,慢慢調(diào)整和豐富自己的個(gè)人經(jīng)驗(yàn)了笑诅。
4. 單一職責(zé)原則與依賴注入
對(duì)于單例模式,我似乎也會(huì)遇到過度設(shè)計(jì)這種情況——每個(gè)對(duì)象的單例都需要再寫一個(gè)類去封裝疮鲫,似乎也太麻煩了吆你。
實(shí)際上這并非過度設(shè)計(jì),因?yàn)檫@種設(shè)計(jì)是必要的棚点,它能夠節(jié)省性能的開銷早处,但是對(duì)象的創(chuàng)建和管理依然是對(duì)開發(fā)者一個(gè)不可小覷的工作量。
此外瘫析,還需要考量的是砌梆,對(duì)于一個(gè)復(fù)雜的單例對(duì)象,它可能有很多的狀態(tài)和依賴贬循,這意味著咸包,單例類的職責(zé)很有可能很重,這在一定程度上違背了單一職責(zé)原則:
一個(gè)類只負(fù)責(zé)一個(gè)功能領(lǐng)域中的相應(yīng)職責(zé)杖虾,或者可以定義為:就一個(gè)類而言烂瘫,應(yīng)該只有一個(gè)引起它變化的原因。
單一職責(zé)原則告訴我們:一個(gè)類不能太“累”奇适! 一個(gè)類的職責(zé)越重(這往往從構(gòu)造器所需要的依賴就能體現(xiàn)出來(lái))坟比,它被復(fù)用的可能性就越小。
在了解了單例模式的優(yōu)點(diǎn)和缺點(diǎn)后嚷往,我們可以有選擇的使用單例模式葛账,對(duì)于依賴過于復(fù)雜的對(duì)象的單例,我們更需要仔細(xì)考量皮仁。
對(duì)于復(fù)雜的依賴管理籍琳,依賴注入庫(kù)(比如Dagger)是一個(gè)可以考慮的解決方案(慎重)菲宴,對(duì)于單例模式的實(shí)現(xiàn),你只需要在Module中對(duì)應(yīng)的依賴Provider上添加一個(gè)@Singleton注解趋急,編譯器會(huì)在編譯期間為您自動(dòng)生成對(duì)應(yīng)的單例模式代碼喝峦。
不能否認(rèn),這個(gè)工具需要相對(duì)較高的學(xué)習(xí)成本呜达,但是學(xué)會(huì)了依賴注入工具并理解了IOC(控制反轉(zhuǎn))谣蠢,DI(依賴注入)的思想之后,它將成為你開發(fā)過程中無(wú)往不勝的利器查近。
5.開閉原則
開閉原則:一個(gè)軟件應(yīng)對(duì)擴(kuò)展開放漩怎、對(duì)修改關(guān)閉,用head first中的話說(shuō)就是:代碼應(yīng)該如晚霞中 的蓮花一樣關(guān)閉(免于改變)嗦嗡,如晨曦中的蓮花一樣開放(能夠擴(kuò)展).
建造者模式(Builder)便是開閉原則的完全體現(xiàn),它將對(duì)象的構(gòu)建和調(diào)用隔離開來(lái)饭玲,不同的使用者都可以通過自由的構(gòu)建對(duì)象侥祭,然后使用它。
6.小結(jié)
創(chuàng)建型模式是最容易入門的茄厘,因?yàn)樵擃愋偷哪J桨?jīng)常暴露在開發(fā)者面前,但是它們并不簡(jiǎn)單次哈,我們除了知道這些模式的使用方式胎署,更應(yīng)該去思考什么時(shí)候用,用哪個(gè)窑滞,甚至是組合使用它們——它們有些互斥琼牧,有些也可以互補(bǔ),這需要我們?nèi)パ芯扛?jīng)典的一些代碼哀卫,并自己作出嘗試巨坊。
不只是創(chuàng)建型,接下來(lái)的結(jié)構(gòu)型和行為型的設(shè)計(jì)模式此改,本文也不會(huì)去一一闡述其目錄下所有的設(shè)計(jì)模式趾撵。
結(jié)構(gòu)型模式
1.定義
首先闡述書中結(jié)構(gòu)型模式的定義:
結(jié)構(gòu)型模式涉及到如何組合類和對(duì)象以獲得更大的結(jié)構(gòu)。結(jié)構(gòu)型類模式采用繼承機(jī)制來(lái)組合接口或?qū)崿F(xiàn)共啃。
在學(xué)習(xí)之初占调,對(duì)我個(gè)人而言,閱讀《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》 的內(nèi)容宛如誦讀天書移剪,書中對(duì)每種設(shè)計(jì)模式都進(jìn)行了詳細(xì)的講解究珊,但是我看完之后,很快就忘掉了挂滓,亦或是對(duì)看起來(lái)非常相似的兩種設(shè)計(jì)模式感到疑惑——書中的講解細(xì)致入微苦银,但是太抽象了啸胧。
最終(也就是現(xiàn)在),我個(gè)人對(duì)于結(jié)構(gòu)型模式的理解是幔虏,通過將不同類或?qū)ο蟮慕M合纺念,采用繼承或者組合接口,或者組合一些對(duì)象想括,以實(shí)現(xiàn)新的功能陷谱。
用一句話陳述,就是對(duì)不同職責(zé)的對(duì)象(以對(duì)象/抽象類/接口的形式)之間組合調(diào)度的實(shí)現(xiàn)方式瑟蜈。
2.并非所有對(duì)象的組合都是結(jié)構(gòu)型模式
實(shí)際上烟逊,并非所有對(duì)對(duì)象的組合都屬于結(jié)構(gòu)型模式,構(gòu)型模式的意義在于铺根,對(duì)一些對(duì)象的組合宪躯,以實(shí)現(xiàn)新功能的方式—— 通過運(yùn)行時(shí),通過改變組合的關(guān)系位迂,這種靈活性產(chǎn)生不同的效果访雪,這種機(jī)制,普通的對(duì)象組合是不可能實(shí)現(xiàn)的掂林。
接下來(lái)我將通過闡述數(shù)種不同的結(jié)構(gòu)型模式在實(shí)際開發(fā)中的應(yīng)用臣缀,逐步加深對(duì)上文敘述的理解。
3.RecyclerView:適配器模式
RecyclerView是Android日常開發(fā)中實(shí)現(xiàn)列表的首選方案泻帮,站在我的角度來(lái)看精置,我還沒想明白一個(gè)問題,RecyclerView是如何實(shí)現(xiàn)列表的锣杂?
我可以回答說(shuō)脂倦,通過實(shí)現(xiàn)RecyclerView.Adapter就能實(shí)現(xiàn)列表呀!
事實(shí)上元莫,是這樣的狼讨,但是這引發(fā)了另外一個(gè)問題,Adapter和RecyclerView之間的關(guān)系是什么柒竞,為啥實(shí)現(xiàn)了Adapter就能實(shí)現(xiàn)RecyclerView呢政供?
思考現(xiàn)實(shí)中的一個(gè)問題,我有一臺(tái)筆記本電腦朽基,我的屋子里也有一個(gè)電源布隔,我如何給我的筆記本充電?
不假思索稼虎,我們用筆記本的充電器連接電源和筆記本就行了衅檀,實(shí)際上,充電器更官方的叫法應(yīng)該叫做電源適配器(Adapter)霎俩。對(duì)于筆記本電腦和電源來(lái)講哀军,它們并沒有直接的關(guān)系沉眶,但是通過Adapter適配器,它們就能產(chǎn)生新的功能——電源給筆記本充電杉适。
RecyclerView和數(shù)據(jù)的展示也是一樣谎倔,數(shù)據(jù)對(duì)象和RecyclerView并沒有直接的關(guān)系,但是我如果想要將數(shù)據(jù)展示在RecyclerView上猿推,通過給RecyclerView配置一個(gè)適配器(Adapter)以連接數(shù)據(jù)源片习,就可以了。
現(xiàn)在我們來(lái)看Adapter模式的定義:
使原本由于接口不兼容而不能一起工作的那些類可以一起工作蹬叭。
現(xiàn)在我們理解了適配器模式的應(yīng)用場(chǎng)景藕咏,但是我想拋出一個(gè)問題:
為啥我要實(shí)現(xiàn)一個(gè)Adapter,設(shè)計(jì)之初秽五,為什么不能直接設(shè)置RecyclerView呢孽查?
比如說(shuō),我既然有了數(shù)據(jù)源坦喘,為什么設(shè)計(jì)之初卦碾,不能讓RecyclerView通過這樣直接配置呢:
mRecyclerView.setDataAndShow(datas);
我的理解是,如果把RecyclerView比喻為屋子里的電源插口起宽,電源不知道它將要連接什么設(shè)備(同樣,RecyclerView也不可能知道它要展示什么樣的數(shù)據(jù)济榨,怎么展示)坯沪,而不同的設(shè)備的接口也可能不一樣,但是只要為設(shè)備配置一個(gè)對(duì)應(yīng)的適配器擒滑,兩個(gè)不相關(guān)的接口就能一起工作腐晾。
RecyclerView的設(shè)計(jì)者將實(shí)現(xiàn)對(duì)開發(fā)者隱藏,并通過Adapter對(duì)開發(fā)者暴露其接口丐一,開發(fā)者通過配置數(shù)據(jù)源(設(shè)備)和對(duì)應(yīng)的適配器(充電器)藻糖,就能實(shí)現(xiàn)列表的展示(充電)。
4.Retrofit:外觀模式與動(dòng)態(tài)代理
說(shuō)到迪米特法則(也叫最少知識(shí)原則)库车,這個(gè)應(yīng)該很好理解,就是降低各模塊之間的耦合:
迪米特法則:一個(gè)軟件實(shí)體應(yīng)當(dāng)盡可能少地與其他實(shí)體發(fā)生作用巨柒。
我的學(xué)習(xí)過程中,讓我感受到設(shè)計(jì)模式的組合之美的第一個(gè)庫(kù)就是Retrofit柠衍,對(duì)于網(wǎng)絡(luò)請(qǐng)求洋满,你只需要配置一個(gè)接口:
public interface BlogService {
@GET("blog/{id}")
Call<ResponseBody> getBlog(@Path("id") int id);
}
// 使用方式
// 1.初始化配置Retrofit對(duì)象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:4567/")
.addConverterFactory(GsonConverterFactory.create())
.build();
// 2.實(shí)例化BlogService接口
BlogService service = retrofit.create(BlogService.class);
Retrofit的源碼中,通過組合珍坊,將各種設(shè)計(jì)模式應(yīng)用在一起牺勾,構(gòu)成了整個(gè)框架,保證了我們常說(shuō)的高內(nèi)聚阵漏,低耦合驻民,堪稱設(shè)計(jì)模式學(xué)習(xí)案例的典范翻具,如下圖(圖片參考感謝這篇文章):
在分析整個(gè)框架的時(shí)候,我們首先從API的使用方式入手回还,我們可以看到裆泳,在配置Retrofit的時(shí)候,庫(kù)采用了外觀模式作為Retrofit的門面懦趋。
有朋友說(shuō)了晾虑,在我看來(lái),Retrofit的初始化仅叫,不應(yīng)該是Builder模式嗎帜篇,為什么你說(shuō)它是外觀模式呢?
我們首先看一下《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》一書對(duì)于外觀模式的定義:
為子系統(tǒng)中的一組接口提供一個(gè)一致的界面诫咱,外觀模式定義一個(gè)高層接口笙隙,這個(gè)接口使得這一子系統(tǒng)更容易使用。
我的解讀是,對(duì)于網(wǎng)絡(luò)請(qǐng)求庫(kù)的Retrofit,它內(nèi)部有著很多不同的組件滥玷,包括數(shù)據(jù)的序列化鉴未,線程的調(diào)度,不同的適配器等往史,這一系列復(fù)雜的子系統(tǒng),對(duì)于網(wǎng)絡(luò)請(qǐng)求來(lái)講,都是不可或缺的且關(guān)系復(fù)雜的,那么拾给,通過將它們都交給Retrofit對(duì)象去配置和調(diào)度(當(dāng)然祥得,Retrofit對(duì)象的創(chuàng)建是通過Builder模式實(shí)現(xiàn)的),對(duì)于API的調(diào)用者來(lái)說(shuō)蒋得,使用配置起來(lái)簡(jiǎn)單方便级及,這符合外觀模式 的定義。
簡(jiǎn)單理解了外觀模式的思想额衙,接下來(lái)我們來(lái)看一下動(dòng)態(tài)代理饮焦,對(duì)于最初接觸Retrofit的我來(lái)說(shuō),我最難以理解的是我只配置了一個(gè)接口窍侧,Retrofit是如何幫我把Service對(duì)象創(chuàng)建出來(lái)的呢追驴?
// 2.實(shí)例化BlogService接口
BlogService service = retrofit.create(BlogService.class);
實(shí)際上,并沒有BlogService這個(gè)對(duì)象的創(chuàng)建疏之,service只不過是在jvm運(yùn)行時(shí)動(dòng)態(tài)生成的一個(gè)proxy對(duì)象殿雪,這個(gè)proxy對(duì)象的意義是:
為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問。
我想通過BlogService進(jìn)行網(wǎng)絡(luò)請(qǐng)求锋爪,Retrofit就會(huì)通過動(dòng)態(tài)代理實(shí)現(xiàn)一個(gè)proxy對(duì)象代理BlogService的行為丙曙,當(dāng)我調(diào)用它的某個(gè)方法請(qǐng)求網(wǎng)絡(luò)時(shí)爸业,實(shí)際上是這個(gè)proxy對(duì)象通過解析你的注解和方法的參數(shù),通過一系列的邏輯包裝成一個(gè)網(wǎng)絡(luò)請(qǐng)求的OkHttpCall對(duì)象亏镰,并請(qǐng)求網(wǎng)絡(luò)扯旷。
現(xiàn)在我明白了,怪不得我無(wú)論怎么給Service的接口和方法命名索抓,Retrofit都會(huì)動(dòng)態(tài)生成代理對(duì)象并在調(diào)用其方法時(shí)進(jìn)行解析钧忽,對(duì)于復(fù)雜多變的網(wǎng)絡(luò)請(qǐng)求來(lái)講,這種實(shí)現(xiàn)的方式非常合適逼肯。
5.里氏替換原則
在優(yōu)秀的源碼中耸黑,我們經(jīng)常可以看到篮幢,很多功能的實(shí)現(xiàn)大刊,都是依賴其接口進(jìn)行的,這里我們首先要理解面向?qū)ο笾凶钪匾幕驹瓌t之一里氏替換原則:
任何基類可以出現(xiàn)的地方三椿,子類一定可以出現(xiàn)缺菌。
里氏代換原則是對(duì)開閉原則的補(bǔ)充。實(shí)現(xiàn)開閉原則的關(guān)鍵步驟就是抽象化搜锰。而基類與子類的繼承關(guān)系就是抽象化的具體實(shí)現(xiàn)伴郁,所以里氏代換原則是對(duì)實(shí)現(xiàn)抽象化的具體步驟的規(guī)范。
向上轉(zhuǎn)型是Java的基礎(chǔ)蛋叼,我們經(jīng)常也用到焊傅,實(shí)際上,在進(jìn)行設(shè)計(jì)的時(shí)候鸦列,盡量從抽象類繼承,而不是從具體類繼承鹏倘。同時(shí)薯嗤,保證在軟件系統(tǒng)中,把父類都替換成它的子類纤泵,程序的行為沒有變化骆姐,就足夠了。
6.小結(jié)
通過上述案例捏题,我們簡(jiǎn)單理解了幾種結(jié)構(gòu)型設(shè)計(jì)模式的概念和思想玻褪,總結(jié)一下:
在解決了對(duì)象的創(chuàng)建問題之后,對(duì)象的組成以及對(duì)象之間的依賴關(guān)系就成了開發(fā)人員關(guān)注的焦點(diǎn)公荧,因?yàn)槿绾卧O(shè)計(jì)對(duì)象的結(jié)構(gòu)带射、繼承和依賴關(guān)系會(huì)影響到后續(xù)程序的維護(hù)性、代碼的健壯性循狰、耦合性等窟社。所以也有多種結(jié)構(gòu)型模式可供開發(fā)人員選擇使用券勺。
提高類之間的協(xié)作效率——行為型模式
1.定義
我們先看書中對(duì)行為型模式比較嚴(yán)謹(jǐn)?shù)亩x:
行為模式涉及到算法和對(duì)象間職責(zé)的分配,行為模式不僅描述對(duì)象或類的模式灿里,還描述它們之間的通信模式关炼。這些模式刻劃了在運(yùn)行時(shí)難以跟蹤的復(fù)雜的控制流,將你的注意力從控制流轉(zhuǎn)移到對(duì)象間的聯(lián)系方式上來(lái)匣吊。
依然是有點(diǎn)難以理解儒拂,我們先舉兩個(gè)例子:
2.OkHttp:Intercepter和職責(zé)鏈模式
在 Okhttp 中, Intercepter就是典型的職責(zé)鏈模式的體現(xiàn).它可以設(shè)置任意數(shù)量的Intercepter來(lái)對(duì)網(wǎng)絡(luò)請(qǐng)求及其響應(yīng)做任何中間處理——設(shè)置緩存, Https的證書驗(yàn)證, 統(tǒng)一對(duì)請(qǐng)求加密/防串改, 打印自定義Log, 過濾請(qǐng)求等。
new OkHttpClient.Builder()
.addNetworkInterceptor(interceptor1)
.addNetworkInterceptor(interceptor2)
.addNetworkInterceptor(interceptor3)
職責(zé)鏈模式的定義為:
讓多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求色鸳,從而避免請(qǐng)求的發(fā)送者和接受者之間的耦合關(guān)系社痛,將他們連成一條鏈,并沿著這條鏈傳遞該請(qǐng)求缕碎,直到有對(duì)象處理它為止褥影。
以現(xiàn)實(shí)為例,職責(zé)鏈模式之一就是網(wǎng)絡(luò)連接咏雌,七層或五層的網(wǎng)絡(luò)連接模型如下:
網(wǎng)絡(luò)請(qǐng)求發(fā)出,經(jīng)過應(yīng)用層->傳輸層->網(wǎng)絡(luò)層->連接層->物理層
收到響應(yīng)后,物理層->連接層->網(wǎng)絡(luò)層->傳輸層->應(yīng)用層
在請(qǐng)求經(jīng)過各層時(shí),由每層輪流處理.每層都可以對(duì)請(qǐng)求或響應(yīng)進(jìn)行處理.并可以中斷鏈接,以自身為終點(diǎn)返回響應(yīng)凡怎。
3.RxJava:觀察者模式
Android開發(fā)中,點(diǎn)擊事件的監(jiān)聽是很經(jīng)典觀察者模式的體現(xiàn):
button.setOnClickListener(v -> {
// do something
})
對(duì)設(shè)置OnClickListener來(lái)說(shuō)赊抖,View是被觀察者统倒,OnClickListener是觀察者,兩者通過setOnClickListener()方法達(dá)成注冊(cè)(訂閱)關(guān)系氛雪。訂閱之后房匆,當(dāng)用戶點(diǎn)擊按鈕,View就會(huì)將點(diǎn)擊事件發(fā)送給已經(jīng)注冊(cè)的 OnClickListener报亩。
同樣浴鸿,對(duì)于可以和Retrofit配套的RxJava來(lái)講,它是也通過觀察者模式來(lái)實(shí)現(xiàn)的弦追。
// 被觀察者
Observable observable = Observable
.just("Hello", "Hi", "Aloha")
// 觀察者
Observer<String> observer = new Observer<String>() {
@Override
public void onNext(String s) {
Log.d(tag, "Item: " + s);
}
@Override
public void onCompleted() {
Log.d(tag, "Completed!");
}
@Override
public void onError(Throwable e) {
Log.d(tag, "Error!");
}
};
// 執(zhí)行訂閱關(guān)系
observable.subscribe(observer);
RxJava強(qiáng)大的異步處理岳链,將數(shù)據(jù)的創(chuàng)建和接收分成了兩部分,對(duì)于觀察者來(lái)說(shuō)劲件,它不關(guān)心數(shù)據(jù)什么時(shí)候發(fā)射的掸哑,怎么發(fā)射的,它只關(guān)心零远,當(dāng)觀察到到最新數(shù)據(jù)時(shí)苗分,怎樣進(jìn)行對(duì)應(yīng)的處理。
我們知道了觀察者模式的這種方式牵辣,我們更需要去深入思考對(duì)于觀察者模式使用前后摔癣,對(duì)我們代碼設(shè)計(jì)系統(tǒng)的影響——它的好處是什么?
最直接的好處是,被觀察者并不知道觀察者的詳細(xì)實(shí)現(xiàn)供填。
就像我剛才所說(shuō)的拐云,被觀察者只負(fù)責(zé)發(fā)射事件,對(duì)于事件如何處理近她,它并不關(guān)心叉瘩,這意味著被觀察者和觀察者之間并不是緊密耦合的,它們可以處于一個(gè)系統(tǒng)中的不同抽象層次粘捎。
不同抽象層次這句話本身就有點(diǎn)抽象薇缅,我們以Button的點(diǎn)擊事件為例,對(duì)于Button來(lái)講攒磨,它是一個(gè)庫(kù)的工具泳桦,它應(yīng)該屬于項(xiàng)目中底層組件,而對(duì)于我們某個(gè)Activity的某個(gè)點(diǎn)擊事件來(lái)講娩缰,它是屬于靠頂部業(yè)務(wù)層的代碼灸撰,可以說(shuō),Button和點(diǎn)擊事件是不在一個(gè)抽象層次拼坎,較低層次的Button可以將點(diǎn)擊事件發(fā)送給較高層次的事件監(jiān)聽器并通知它浮毯。
而如果不采用這種方式,觀察者和被觀察者就必須混在一起泰鸡,這樣對(duì)象就會(huì)橫貫項(xiàng)目的2個(gè)層次(違反了層次性)债蓝,或者必須放在這兩層中的某一層中(可能會(huì)損害層次抽象)。
將底層組件按鈕被點(diǎn)擊后行為盛龄,抽象出來(lái)交給較高層級(jí)去實(shí)現(xiàn)饰迹,了解了這種方式的好處,依賴倒置原則就不難理解了余舶。
4.依賴倒置原則
現(xiàn)在我們來(lái)了解一下依賴倒置原則:
抽象不應(yīng)該依賴于細(xì)節(jié)啊鸭,細(xì)節(jié)應(yīng)當(dāng)依賴于抽象。換言之匿值,要針對(duì)接口編程赠制,而非針對(duì)實(shí)現(xiàn)編程。
它的原則是:
- 1.高層模塊不應(yīng)該依賴于低層模塊千扔,兩個(gè)都應(yīng)該依賴于抽象憎妙。
- 2.抽象不應(yīng)該依賴細(xì)節(jié)库正,細(xì)節(jié)應(yīng)該依賴于抽象曲楚。
在java中,抽象指的是接口或者抽象類褥符,細(xì)節(jié)就是具體的實(shí)現(xiàn)類龙誊,使用接口或者抽象類的目的是制定好規(guī)范,而不去涉及任何具體的操作喷楣,把展現(xiàn)細(xì)節(jié)的任務(wù)交給他們的實(shí)現(xiàn)類去完成趟大。
了解了依賴倒置原則鹤树,我們?cè)俳釉賲枺瑢W(xué)習(xí)最后一個(gè)設(shè)計(jì)模式的基本原則:
5.接口隔離原則
接口隔離原則:客戶端不應(yīng)該依賴它不需要的接口逊朽;一個(gè)類對(duì)另一個(gè)類的依賴應(yīng)該建立在最小的接口上罕伯。
這個(gè)應(yīng)該是最好理解的原則了,它的意義就是:使用多個(gè)專門的接口比使用單一的總接口要好叽讳。
這很好理解追他,對(duì)于鳥的實(shí)現(xiàn)(Bird),我們可以定義兩個(gè)功能接口岛蚤,分別是Fly和Eat邑狸,我們可以讓Bird分別實(shí)現(xiàn)這兩個(gè)接口——如果我們還有一個(gè)Dog,那么對(duì)于Eat接口涤妒,可以復(fù)用单雾,但是如果只有一個(gè)接口(包含F(xiàn)ly和Eat兩個(gè)功能),對(duì)于Dog來(lái)說(shuō)她紫,它是不會(huì)飛(Fly)的硅堆,那么就需要針對(duì)Dog再聲明一個(gè)新的接口,這是沒有必要的設(shè)計(jì)犁苏。
6.小結(jié)
在對(duì)象的結(jié)構(gòu)和對(duì)象的創(chuàng)建問題都解決了之后硬萍,就剩下對(duì)象的行為問題了,如果對(duì)象的行為設(shè)計(jì)的好围详,那么對(duì)象的行為就會(huì)更清晰朴乖,它們之間的協(xié)作效率就會(huì)提高。
現(xiàn)在我們?cè)倏瓷衔闹袑?duì)行為型模式比較嚴(yán)謹(jǐn)?shù)亩x助赞,相信大家能夠理解一些了:
行為模式涉及到算法和對(duì)象間職責(zé)的分配买羞,行為模式不僅描述對(duì)象或類的模式,還描述它們之間的通信模式雹食。這些模式刻劃了在運(yùn)行時(shí)難以跟蹤的復(fù)雜的控制流畜普,將你的注意力從控制流轉(zhuǎn)移到對(duì)象間的聯(lián)系方式上來(lái)。
喘口氣
關(guān)于設(shè)計(jì)模式相關(guān)的講解內(nèi)容群叶,到此基本就告一段落了吃挑。
等等...
我一個(gè)設(shè)計(jì)模式都沒學(xué)會(huì),你TM告訴我你講完了街立?
一無(wú)所獲舶衬?
本文并沒有去通過代碼簡(jiǎn)單描述各個(gè)設(shè)計(jì)模式的實(shí)現(xiàn)方式,于我而言毫無(wú)意義赎离,設(shè)計(jì)思想是通過不斷思考逛犹,理解并在親身嘗試中學(xué)會(huì)的,短時(shí)間快速大量閱讀學(xué)習(xí)的方式效果甚微,即使學(xué)會(huì)了虽画,如何將sample中的設(shè)計(jì)思想舞蔽,轉(zhuǎn)換為實(shí)際產(chǎn)品中復(fù)雜的業(yè)務(wù)設(shè)計(jì),這也是一個(gè)非常大的難題码撰。
在這里渗柿,我引用《倚天屠龍記》中我最喜歡的經(jīng)典片段, 就是張三豐在武當(dāng)山當(dāng)著敵人的面教張無(wú)忌太極劍那段。
“只聽張三豐問道:‘孩兒脖岛,你看清楚了沒有做祝?’張無(wú)忌道:‘看清楚了〖Ω冢’張三豐道: ‘都記得了沒有混槐?’張無(wú)忌道:‘已忘記了一小半⌒裕’張三豐道:‘好声登,那也難為了你。你自己去想想罷揣苏∶跎ぃ’張無(wú)忌低頭默想。過了一會(huì)卸察,張三豐問道:‘現(xiàn)下怎樣了脯厨?’張無(wú)忌道: ‘已忘記了一大半】又剩’
周顛失聲叫道:‘糟糕合武!越來(lái)越忘記得多了。張真人涡扼,你這路劍法很是深?yuàn)W稼跳,看一遍怎能記得?請(qǐng)你再使一遍給我們教主瞧瞧罷吃沪√郎疲’
張三豐微笑道:‘好,我再使一遍票彪『斓’提劍出招,演將起來(lái)降铸。眾人只看了數(shù)招在旱,心下大奇,原來(lái)第二次所使垮耳,和第一次使的竟然沒一招相同颈渊。周顛叫道:‘糟糕,糟糕终佛!這可更加叫人胡涂啦俊嗽。’張三豐畫劍成圈铃彰,問道:‘孩兒绍豁,怎樣啦?’張無(wú)忌道:‘還有三招沒忘記牙捉≈褡幔’張三豐點(diǎn)點(diǎn)頭,收劍歸座邪铲。
張無(wú)忌在殿上緩緩踱了一個(gè)圈子芬位,沉思半晌,又緩緩踱了半個(gè)圈子带到,抬起頭來(lái)昧碉,滿臉喜色,叫道:‘這我可全忘了揽惹,忘得干干凈凈的了被饿。’張三豐道:‘不壞不壞搪搏!忘得真快狭握,你這就請(qǐng)八臂神劍指教罷!’”
總結(jié)
關(guān)于設(shè)計(jì)模式疯溺,我的理解是论颅,不要拘泥于其概念,只有深刻理解了其設(shè)計(jì)的思想囱嫩,理解之后嗅辣,親自去嘗試使用它,在使用的過程中加深對(duì)這種思想的理解挠说,我想比通過書籍或者博客一個(gè)一個(gè)的去學(xué)澡谭,效果要更好。
我學(xué)習(xí)它們的方式是损俭,學(xué)習(xí)一些優(yōu)秀開源庫(kù)的源碼蛙奖,思考為什么這里使用這些設(shè)計(jì)模式,之后再參考《設(shè)計(jì)模式》一書的相關(guān)概念杆兵,最后自己去嘗試并加深理解雁仲。
于我而言,這種方式的短板在于剛開始的時(shí)候琐脏,可能需要更多的時(shí)間去學(xué)習(xí)攒砖,很多庫(kù)的源碼在初接觸時(shí)缸兔,并非容易理解,但是好處是吹艇,當(dāng)學(xué)會(huì)之后惰蜜,這種思想就很難再?gòu)哪愕挠洃浿信艿袅耍沂苌瘢趯懘a時(shí)抛猖,會(huì)下意識(shí)嘗試使用這些模式(當(dāng)然,更多情況是打開2個(gè)窗口鼻听,一邊參考源碼财著,一邊學(xué)習(xí)將其融入到自己的代碼中)。
設(shè)計(jì)模式相對(duì)于分類和概念正如太極拳(劍)撑碴,它更是一種思想撑教,一種應(yīng)對(duì)變化的解決思想——我不認(rèn)為本文自己的學(xué)習(xí)方式和心得,能夠適合每一個(gè)正在閱讀本文的讀者醉拓,但是如果本文對(duì)您對(duì)技術(shù)成長(zhǎng)的道路上有一些幫助驮履,對(duì)我而言這就是值得的。
參考
1.菜鳥教程——設(shè)計(jì)模式:
http://www.runoob.com/design-pattern/design-pattern-tutorial.html
2.《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》
伽瑪?shù)戎溃焕钣④姷茸g玫镐,機(jī)械工業(yè)出版社,2015年12月第一版37次印刷
3.Retrofit分析-漂亮的解耦套路:
https://blog.piasy.com/2016/06/25/Understand-Retrofit/
4.創(chuàng)建型模式怠噪、結(jié)構(gòu)型模式和行為型模式之間的關(guān)系
https://blog.csdn.net/qq_34583891/article/details/70853637