面向对象之设计模式
面向?qū)ο笾O(shè)計模式
前言
要想推開架構(gòu)師的那扇大門,就離不開設(shè)計模式這把鑰匙。
- 對設(shè)計模式的理解與精通,是通往架構(gòu)師之路的第一步
在任何面向?qū)ο笳Z言的開發(fā)過程以及個人職業(yè)技能成長的道路中,新手與新手或者新手與高手的對決中,決定成敗的往往是對知識點的縱向熟悉,和對知識點橫向的涉獵
- 縱向:表示的對某一個知識點了解的有多深
- 橫向:表示知識點面的廣泛
而高手與高手的對決,決定成敗的往往是對設(shè)計模式的理解與運用
- 遺憾的是,對設(shè)計模式的理解只可意會,不可言傳。很難通過文字和講解口頭闡述,只能自身通過大量的代碼和項目的積累產(chǎn)生自己的見解
- 這需要足夠的興趣和意志力。 技術(shù),是一筆深情課
- 遺憾的是,對設(shè)計模式的理解只可意會,不可言傳。很難通過文字和講解口頭闡述,只能自身通過大量的代碼和項目的積累產(chǎn)生自己的見解
1.1-設(shè)計模式簡介
1.1.1-什么是設(shè)計模式?
- 設(shè)計模式(Design pattern)是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計經(jīng)驗的總結(jié)。使用設(shè)計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設(shè)計模式于己于他人于系統(tǒng)都是多贏的;設(shè)計模式使代碼編制真正工程化;設(shè)計模式是軟件工程的基石脈絡(luò),如同大廈的結(jié)構(gòu)一樣
- 本人對設(shè)計模式的理解是:任何的設(shè)計模式的應(yīng)用都離不開三點:代碼的拓展性、可維護性、生存性
1.1.2-iOS有哪些設(shè)計模式?
- 本小節(jié)主要參考《Objective-c編程之道 iOS設(shè)計模式解析》一書
- iOS有21種設(shè)計模式
- 1.原型模式
- 2.工廠模式
- 3.抽象模式
- 4.生成器模式
- 5.單例模式
- 6.適配器模式
- 7.橋接模式
- 8.外觀模式
- 9.中間者模式
- 10.觀察者模式
- 11.組合模式
- 12.迭代器模式
- 13.訪問者模式
- 14.裝飾模式
- 15.責任鏈模式
- 16.模板模式
- 17.策略模式
- 18.命令模式
- 19.亨元模式
- 20.代理模式
- 21.備忘錄模式
1.2-設(shè)計模式的六大原則
只需了解即可,課后感興趣的可以慢慢咀嚼,設(shè)計模式的六大原則一個比一個難理解,一個比一個更深奧,單純的靠文字和課堂講解很難真正的掌握,必須要大量的代碼積累和不斷的思考感悟
描述設(shè)計模式的原則,這里筆者主要從三個角度分析
- 原則定義
- 原則由來
- 解決方案
***1.單一原則***
- 定義:一個類只負責一項職責
- 由來:如果類A的職責是完成功能P,類B的職責是完成功能Q。如果將功能Q的任何代碼放到類A中,那么一旦類A發(fā)生變更,即使本該完成Q功能的類B代碼沒有任何變更,Q功能也會產(chǎn)生一定的故障。
- 解決方案:如果類A原本的職責是完成功能P,如果功能P在職責之外發(fā)生需求上的變更或者需要拓展,這個時候不可以在類A中修改代碼,應(yīng)該是新建一個類B來完成新增的功能Q
- 但是如果P功能是在職責之內(nèi)發(fā)生變更,可以在類A中修改
***2.里氏替換原則***
- 定義:在程序P中,類B繼承于類A,如果將P程序中的所有的類A的對象替換成類B的對象,這個程序不會發(fā)生任何的變化
- 由來:有一功能R,由類A完成。現(xiàn)需要將功能R進行擴展,擴展后的功能為Q,其中Q由原有功能R與新功能P組成。新功能P由類A的子類B來完成,則子類B在完成新功能P的同時,有可能會導(dǎo)致原有功能R發(fā)生故障
- 解決方案:子類繼承父類的同時,遵循里氏替換原則,只在父類的基礎(chǔ)上拓展功能,而避免重寫或重載父類的方法,更改父類的功能。
- 在不更改父類原有的功能的基礎(chǔ)上,可以酌情重寫父類的方法在父類原有功能上進行拓展
- 所以問題的核心并不是重寫,而是更改
- 在不更改父類原有的功能的基礎(chǔ)上,可以酌情重寫父類的方法在父類原有功能上進行拓展
***3.依賴倒置原則***
- 定義:1.高層次的模塊不應(yīng)該依賴于低層次的模塊,他們都應(yīng)該依賴于抽象。2.抽象不應(yīng)該依賴于具體實現(xiàn),具體實現(xiàn)應(yīng)該依賴于抽象
- 依賴倒置原則(Dependence Inversion Principle)是程序要依賴于抽象接口,不要依賴于具體實現(xiàn)。簡單的說就是要求對抽象進行編程,不要對實現(xiàn)進行編程,這樣就降低了客戶與實現(xiàn)模塊間的耦合。
由來:耦合度太高導(dǎo)致類與類之間的依賴性太強
- 在類A調(diào)用類B的方法P,而方法P卻依賴于類C,如果需求發(fā)生變更,類B中的方法需要依賴于類D,那么就必須要修改類B的代碼,這樣每一個變更都要修改類B的代碼,而類B的邏輯比較多,屬于高層模塊,修改起來會導(dǎo)致其他一些問題。這主要是由于類B與類C之間的偶爾性太強導(dǎo)致。
- 假如有一個基類E,本身沒有任何實現(xiàn),只是指明了類E與類B之間的依賴關(guān)系,并且有一個接口。那么當類B需要依賴類C的時候,只需要指明類B依賴的類E的接口指向類C就可以。這樣的話需求發(fā)生變更就不在需要修改類B的代碼,甚至也不需要修改類E的代碼,因為類E本身是一個虛擬類,沒有任何的實現(xiàn),我們只需要增加修改的代碼放入類E的接口中即可。
- 在類A調(diào)用類B的方法P,而方法P卻依賴于類C,如果需求發(fā)生變更,類B中的方法需要依賴于類D,那么就必須要修改類B的代碼,這樣每一個變更都要修改類B的代碼,而類B的邏輯比較多,屬于高層模塊,修改起來會導(dǎo)致其他一些問題。這主要是由于類B與類C之間的偶爾性太強導(dǎo)致。
解決方案:解耦
- 低層模塊盡量都要有抽象類或接口,或者兩者都有。
- 變量的聲明類型盡量是抽象類或接口。
- 使用繼承時遵循里氏替換原則。
- 定義:1.高層次的模塊不應(yīng)該依賴于低層次的模塊,他們都應(yīng)該依賴于抽象。2.抽象不應(yīng)該依賴于具體實現(xiàn),具體實現(xiàn)應(yīng)該依賴于抽象
依賴倒置原則的核心就是要我們面向接口編程,理解了面向接口編程,也就理解了依賴倒置
***4.接口隔離原則***
- 定義:客戶端不應(yīng)該依賴它不需要的接口;一個類對另一個類的依賴應(yīng)該建立在最小的接口上
- 問題由來:在依賴倒置原則的基礎(chǔ)上,有一個接口類A,類A有五個方法,分別是方法A1,A2,A3,A4,A5;其中類B通過方法A1,A2,A3依賴于類C,類D通過方法A4,A5,A6依賴于類E。那么對于B而言,接口類A的方法A4,A5是多余的,對于類D而言,接口類A的方法A1,A2,A3是多余的。結(jié)果本來好好的一個抽象接口類遵循了依賴倒置原則,結(jié)果對于類B和類D而言都存在臃腫的部分。
- 解決方案:將臃腫的接口類A,分離成獨立的幾個接口類,如類L,類M,類N,這三個接口類分別承擔類A的原有的五個方法,這樣的話類B和類D通過多個接口類去依賴對應(yīng)的類,也就是接口隔離原則。
使用接口隔離原則需要注意以下幾點:
- 接口盡量小,但是要有限度。對接口進行細化可以提高程序設(shè)計靈活性是不掙的事實,但是如果過小,則會造成接口數(shù)量過多,使設(shè)計復(fù)雜化。所以一定要適度。
- 為依賴接口的類定制服務(wù),只暴露給調(diào)用的類它需要的方法,它不需要的方法則隱藏起來。只有專注地為一個模塊提供定制服務(wù),才能建立最小的依賴關(guān)系
- 提高內(nèi)聚,減少對外交互。使接口用最少的方法去完成最多的事情
運用接口隔離原則,一定要適度,接口設(shè)計的過大或過小都不好。設(shè)計接口的時候,只有多花些時間去思考和籌劃,才能準確地實踐這一原則
接口隔離原則與OC的多重繼承沖突的解決方案
- 接口隔離的原則實際上就是多重繼承進行接口分離,但是OC不支持多重繼承,所以在iOS開發(fā)中一般使用協(xié)議來進行接口分離,這就是為什么有的系統(tǒng)類同時遵循了好幾個協(xié)議的原因
***5.迪米特法則***
- 定義:一個對象應(yīng)該對其他對象保持最少的了解
- 問題由來:類與類之間的關(guān)系越密切,耦合度越大,當一個類發(fā)生改變時,對另一個類的影響也越大。
- 解決方案:盡量降低類與類之間的耦合
- 主要針對的是具體的實現(xiàn)類,在設(shè)計的時候應(yīng)該在遵循其他原則的基礎(chǔ)上最大限度的保證高內(nèi)聚和低耦合
自從我們接觸編程開始,就知道了軟件編程的總的原則:低耦合,高內(nèi)聚。無論是面向過程編程還是面向?qū)ο缶幊?#xff0c;只有使各個模塊之間的耦合盡量的低,才能提高代碼的復(fù)用率。低耦合的優(yōu)點不言而喻,但是怎么樣編程才能做到低耦合呢?那正是迪米特法則要去完成的
- 迪米特法則又叫最少知道原則,最早是在1987年由美國Northeastern University的Ian Holland提出。通俗的來講,就是一個類對自己依賴的類知道的越少越好。也就是說,對于被依賴的類來說,無論邏輯多么復(fù)雜,都盡量地的將邏輯封裝在類的內(nèi)部,對外除了提供的public方法,不對外泄漏任何信息。迪米特法則還有一個更簡單的定義:只與直接的朋友通信。首先來解釋一下什么是直接的朋友:每個對象都會與其他對象有耦合關(guān)系,只要兩個對象之間有耦合關(guān)系,我們就說這兩個對象之間是朋友關(guān)系。耦合的方式很多,依賴、關(guān)聯(lián)、組合、聚合等。其中,我們稱出現(xiàn)成員變量、方法參數(shù)、方法返回值中的類為直接的朋友,而出現(xiàn)在局部變量中的類則不是直接的朋友。也就是說,陌生的類最好不要作為局部變量的形式出現(xiàn)在類的內(nèi)部。
使用迪米特法則的時候可以聯(lián)想一下公司的為人處事之道,假如你是員工,你的上面有主管,然后有總監(jiān),再上面有老板。當你有一件事只跟你的老板有關(guān),那么如果你直接找你的老板,這就是屬于越級的行為(與單一原則產(chǎn)生沖突)。而你先找你的主管,再通過主管找總監(jiān),最后通過總監(jiān)找到老板,這個過程即復(fù)雜又讓跟這件事沒有關(guān)系的人知道了(與迪米特法則自身沖突),是不是突然變得非常棘手不知道怎么處理呢?所以有時候合理的理解設(shè)計模式需要強大的情商和悟性
- 迪米特法則的初衷是降低類之間的耦合,由于每個類都減少了不必要的依賴,因此的確可以降低耦合關(guān)系。但是凡事都有度,雖然可以避免與非直接的類通信,但是要通信,必然會通過一個“中介”來發(fā)生聯(lián)系,例如本例中,總公司就是通過分公司這個“中介”來與分公司的員工發(fā)生聯(lián)系的。過分的使用迪米特原則,會產(chǎn)生大量這樣的中介和傳遞類,導(dǎo)致系統(tǒng)復(fù)雜度變大。所以在采用迪米特法則時要反復(fù)權(quán)衡,既做到結(jié)構(gòu)清晰,又要高內(nèi)聚低耦合。
- 迪米特法則最核心的內(nèi)容是在設(shè)計類時,對類的耦合度和內(nèi)聚度有一個合理的綜合權(quán)衡,而并未單獨的偏向一方
- 解決迪米特法則的沖突問題的時候其實可以考慮到21種設(shè)計模式中的某一些設(shè)計模式來中和某些沖突
- 比如使用中間人模式(又叫中介者模式)
- 解決迪米特法則的沖突問題的時候其實可以考慮到21種設(shè)計模式中的某一些設(shè)計模式來中和某些沖突
- 迪米特法則最核心的內(nèi)容是在設(shè)計類時,對類的耦合度和內(nèi)聚度有一個合理的綜合權(quán)衡,而并未單獨的偏向一方
- 迪米特法則的初衷是降低類之間的耦合,由于每個類都減少了不必要的依賴,因此的確可以降低耦合關(guān)系。但是凡事都有度,雖然可以避免與非直接的類通信,但是要通信,必然會通過一個“中介”來發(fā)生聯(lián)系,例如本例中,總公司就是通過分公司這個“中介”來與分公司的員工發(fā)生聯(lián)系的。過分的使用迪米特原則,會產(chǎn)生大量這樣的中介和傳遞類,導(dǎo)致系統(tǒng)復(fù)雜度變大。所以在采用迪米特法則時要反復(fù)權(quán)衡,既做到結(jié)構(gòu)清晰,又要高內(nèi)聚低耦合。
***6.開閉原則***
- 定義:一個軟件實體如類、模塊和函數(shù)應(yīng)該對擴展開放,對修改關(guān)閉
- 由來:在軟件的生命周期內(nèi),因為變化、升級和維護等原因需要對軟件原有代碼進行修改時,可能會給舊代碼中引入錯誤,也可能會使我們不得不對整個功能進行重構(gòu),并且需要原有代碼經(jīng)過重新測試
- 解決方案:當軟件需要變化時,盡量通過擴展軟件實體的行為來實現(xiàn)變化,而不是通過修改已有的代碼來實現(xiàn)變化
我曾經(jīng)看過一些架構(gòu)師的書籍,其中提到我們進行設(shè)計的時候一定要遵守開閉原則,當時覺得這句話說了等于沒說。到今天回過頭來在看一次,貌似又什么都說了。
- 開閉原則是六大原則中最抽象和虛擬的原則
開閉原則 = 前面五大原則總和的平均分
- 開閉原則無非就是想表達這樣一層意思:用抽象構(gòu)建框架,用實現(xiàn)擴展細節(jié)。因為抽象靈活性好,適應(yīng)性廣,只要抽象的合理,可以基本保持軟件架構(gòu)的穩(wěn)定。而軟件中易變的細節(jié),我們用從抽象派生的實現(xiàn)類來進行擴展,當軟件需要發(fā)生變化時,我們只需要根據(jù)需求重新派生一個實現(xiàn)類來擴展就可以了。當然前提是我們的抽象要合理,要對需求的變更有前瞻性和預(yù)見性才行
- 也就是說前面的五大原則遵循好了,你自己而然就遵守了開閉原則,如果前面五個沒有遵守好,也就沒有遵守開閉原則。開閉原則是對前面五個原則的總結(jié)和約束!
- 開閉原則無非就是想表達這樣一層意思:用抽象構(gòu)建框架,用實現(xiàn)擴展細節(jié)。因為抽象靈活性好,適應(yīng)性廣,只要抽象的合理,可以基本保持軟件架構(gòu)的穩(wěn)定。而軟件中易變的細節(jié),我們用從抽象派生的實現(xiàn)類來進行擴展,當軟件需要發(fā)生變化時,我們只需要根據(jù)需求重新派生一個實現(xiàn)類來擴展就可以了。當然前提是我們的抽象要合理,要對需求的變更有前瞻性和預(yù)見性才行
***六大原則總結(jié)***
- 圖中的每一條維度各代表一項原則,我們依據(jù)對這項原則的遵守程度在維度上畫一個點,則如果對這項原則遵守的合理的話,這個點應(yīng)該落在紅色的同心圓內(nèi)部;如果遵守的差,點將會在小圓內(nèi)部;如果過度遵守,點將會落在大圓外部。一個良好的設(shè)計體現(xiàn)在圖中,應(yīng)該是六個頂點都在同心圓中的六邊形。
在上圖中,設(shè)計1、設(shè)計2屬于良好的設(shè)計,他們對六項原則的遵守程度都在合理的范圍內(nèi);設(shè)計3、設(shè)計4設(shè)計雖然有些不足,但也基本可以接受;設(shè)計5則嚴重不足,對各項原則都沒有很好的遵守;而設(shè)計6則遵守過渡了,設(shè)計5和設(shè)計6都是迫切需要重構(gòu)的設(shè)計
本小節(jié)參考博客http://www.uml.org.cn/sjms/201211023.asp
- 本小節(jié)參考書籍《設(shè)計模式之禪》《面向模式的軟件架構(gòu)》
總結(jié)
- 上一篇: 14-CoreData兼容iOS9和iO
- 下一篇: MVC和MVVM以及MVP的介绍