如何通俗理解設計模式及其思想
術與道
數據結構,算法,設計模式被認為是程序員必備技能的三叉戟,如果說編程語言的語法特性和業務編碼能力是【術】,那麽這三者可以稱得上是【道】——【術】可以讓你在IT行業賴以生存,而【道】則決定你未來在技術這條道路上可以走多遠。
邊學邊忘的窘境
先自我介紹一下。
我是一個兩年工作經驗的Android開發人員,就像很多同行一樣,對於數據結構,算法,設計模式這些被奉為程序員必修的三門內功,幾乎沒有去系統性地學習過(曾經專業課的數據結構,如今也基本還給了老師)。
你問我想不想當一個稱職的程序員,當然!數據結構,算法以及設計模式不止一次的被我列入學習計劃中,我認為這是有意義並且必須的,並且,我也嘗試在閑暇之余去主動學習它們。
但是很遺憾,至今為止,有關於數據結構和算法,我依然處於入門階段,原因就是:
我學會沒多久,一直沒機會用到,然後就忘了!
如果您翻看過我之前的 相關博客 或者我 Github 的 這個倉庫,就能發現,我曾經關於數據結構和算法,都做出過嘗試,但是遺憾的是,我並不能學以致用 ——它們匆匆而來,匆匆而去,就好像生命中的過客一樣。
至於設計模式,因為空閑時間學習的時間並不多,雖然我也經常通過博客或者書籍學習設計模式,但是結果依然沒有太大的改變:過於抽象的概念,加上不經常使用,讓我很快把這些概念忘得差不多了。
我覺得這種學習方式效率不是很高,於是我決定換一種學習的方式——通過閱讀Github上那些開源庫的源碼,學習其中的設計思想和理念。
動機
和大多數人一樣,我只是在編程行業眾多平凡的開發者中的一員,在我職業生涯的伊始,我沒有接觸過技術大牛, 但是閱讀源碼可以讓我零距離碰撞全球行業內最頂尖工程師們的思想,我認為對我而言這是一種效果不錯的學習方式,讓我受益匪淺。
事實上,在要寫這篇博客的時候,我依然忐忑不安,我擔心一篇文章如果寫的不夠好,會給讀者帶來誤導。
我最終鼓起勇氣寫這篇文章的目的是:我想通過分享個人對於設計模式的理解,以及自己的學習方式和所得,這種學習方式可能並非適用於所有人,但是它至少能給予需要的人一個參考。
設計模式的分類
我們先看設計模式的分類:
範圍 | 創建型 | 結構型 | 行為型 |
---|---|---|---|
類 | Factory Method(工廠方法) | Adapter(類) (適配器) | Interpreter(解釋器) Template Method(模版方法) |
對象 | Abstract Factory(抽象工廠) Builder(建造者) Prototype(原型) Singleton(單例) |
Bridge(橋接) Composite(組合) Decorator(裝飾者) Fa?ade(外觀) Flyweight(享元) Proxy(代理) |
Chain of Responsibility(職責鏈) Command(命令) Iterator(叠代器) Mediator(中介者) Memento(備忘錄) Observer(觀察者) State(狀體) Strategy(策略) Visitor(訪問者) |
這是我從 這篇文章 中找到的對設計模式的歸納。
同時,我們需要了解到,設計模式的6個基本原則(這裏先列出來,接下來會參考案例一個個解釋):
- 1、單一職責原則(Single Responsibility Principle)
- 2、裏氏代換原則(Liskov Substitution Principle)
- 3、依賴倒轉原則(Dependence Inversion Principle)
- 4、接口隔離原則(Interface Segregation Principle)
- 5、迪米特法則,又稱最少知道原則(Demeter Principle)
- 6、開閉原則(Open Close Principle)
在設計模式的學習過程中,這些設計模式並非是按照不同類型循序漸進講解的,更多的場景是,多個不同類型的設計模式相互組合——最終展示出來的是一個完整的架構設計體系。這種設計模式復雜組合帶來的好處是:高內聚,低耦合,這使得庫本身的拓展非常簡單,同時也非常便於單元測試。
當然,對於通過源碼,想一窺設計思想的學習者來說,額外的接口,以及可能隨之額來額外的代碼會需要更多的學習成本,對於最初的我來說,復雜的設計真的給我帶來了很大的困擾,我試圖去理解和反思這樣設計的好處——它的確花費了我更多的時間,但是更讓我受益匪淺。
最初的收獲——創建型模式
在Android學習的過程中,我最先接觸到的就是創建型模式,所謂創建型模式,自然與對象的創建有關。
實際上,不談源碼,實際開發中,我們也遇到了很多創建型模式的體現,最常見的當屬單例模式和建造者模式(Builder)。
1.“最簡單”的設計模式
我們以單例模式為例,他的定義是:
“一個類有且僅有一個實例,並且自行實例化向整個系統提供。”
相信大家對這個單例模式並不陌生,它被稱為 “設計模式中最簡單的形式之一”,它很簡單,並且易於理解,開發者總能遇到需要持有唯一對象的業務需求。
以Android開發為例,經常需要在某個類中,使用到Application對象,它本身是唯一的,因此我們只需要通過一個類持有它的靜態引用,然後通過靜態方法獲取就可以了。
另外的一種需求是,某個類的對象會占用很大的內存,我們也沒有必要對這個類實例化兩次,這樣,保持其對象的類單例,能夠省下更多的性能空間,比如Android的數據庫db的引用。
實際上,單例模式的細分下來,有很多種實現方式,比如眾所周知的懶漢式,餓漢式,Double CheckLock,靜態內部類,枚舉,這些不同的單例實現方式,都有各自的優缺點(比如是否線程安全),也對應著不同的適用場景,這也正是單例模式作為看起來“最簡單”同時也是面試中的重點考察項目的原因。
這些不同的實現方式,百度上講解的非常詳細,本文不贅述。
我們需要理解的是,我們什麽時候使用單例模式。
對於系統中的某些類來說,只有一個實例很重要,比如上述的Application,這很好理解,實際上,在開發過程中,我們更需要關註一些細節的實現。
比如對Gson的單例。
實際開發中,調用Gson對象進行轉換的地方非常多,如果在調用的地方每次new Gson的話,是影響性能的。
Gson本身是線程安全的,它可以被多個線程同時使用,因此,我更傾向於通過下面的方式獲取Gson的實例:
public class Gsons {
private static class Holder {
private static final Gson INSTANCE = new Gson();
}
public static Gson getInstance() {
return Holder.INSTANCE;
不僅是Gson, 除此之外還有比如網絡請求的相關管理類(Retrofit對象,ServiceManager等),Android系統提供的各種XXXManager(NotificationManager)等等,這些通過單例的方式去管理它,能夠讓你業務設計的更加嚴謹。
2.Builder的鏈式調用
建造者模式(Builder Pattern)使用多個簡單的對象一步一步構建成一個復雜的對象。
Android開發者一定很熟悉它,因為我們創建AlertDialog的時候,鏈式調用的API實在是賞心悅目:
new AlertDialog
.Builder(this)
.setTitle("標題")
.setMessage("內容")
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//...
}
})
.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
此外,稍微細心的同學會發現,其實JDK中,StringBuilder和StringBuffer的源碼中的append()方法也是Builder模式的體現:
public StringBuilder append(String str) {
super.append(str); // 調用基類的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; // 返回構建對
除了賞心悅目的代碼之外,我更關註Builder模式的使用場景:
當我們面臨著一個復雜對象的創建工作,其通常由各個部分的子對象用一定的算法構成;由於需求的變化,這個復雜對象的各個部分經常面臨著劇烈的變化,但是將它們組合在一起的算法卻相對穩定。
很好,我把 這個學習網站 關於Builder模式的適用場景復制下了下來,我曾經在學習它的時候嘗試去理解它的敘述,所得出的結論是 ——上文的定義非常嚴謹,但是我看不懂。
我們參考AlertDialog,對於一個Dialog而言,它的基本構成是復雜的(有標題,內容,按鈕及其對應的事件等等屬性),但是在實際需求中,不同的界面,我們需要展示給用戶的Dialog是不一樣的(標題不一樣,內容不一樣,點擊事件也不一樣),這些各個部分都是在不斷劇烈的變化,但是他們組合起來是相對穩定的(就是一個Dialog彈出展示在界面上)。
在這種情況下,我們可以嘗試使用Builder模式,和普通的構造器生成對象不同,如果沒有需求,我們可以忽略配置某些屬性——對於Dialog,我可以不去定義title,也可以不去定義取消按鈕的點擊事件,他們內部都有默認的處理;此外,對於API的設計來講,Builder模式更利於去擴展新的功能或者屬性。
Builder模式在我們開發中非常常見,除上述案例之外,Android流行的圖片加載庫,以及Notification通知的實例化等等,都能看到Builder的身影。
上文說到,Builder模式對於對象的創建提供了非常賞心悅目的API,我理解了Builder模式的思想和實現方式之後,便嘗試給自己的一些工具類加一些這樣的設計。
很快,我遇到了一個問題,那就是——這樣寫太TM累了!
3.避免過度設計
關於過度設計的定義,請參考 什麽是軟件開發中的過度設計? 的解釋,我認為講解的非常風趣且易懂。
從我個人的角度而言,我遇到了問題,我嘗試給一些工具改為Builder實現,結果是,我添加了很多很多代碼,但是效果平平。
不僅如此,這樣的設計給我的工具帶來了更多的復雜度,本來一個構造器new一下能解決的問題,非要很多行代碼鏈式配置,這種設計,做了還不如不做。
這樣的結果,讓我對網絡上一位前輩的總結非常贊同,那就是:
設計模式的一個重要的作用是代碼復用,最終的目的是提升效率。
所以,一個模式是否適合或必要,只要看看它是否能減少我們的工作,提升我們的工作效率。
那麽,如何避免過度設計,我的經驗告訴我,寫代碼之前多思考,考慮不同實現方式所需的成本,保證代碼的不斷叠代和調整。
即使如此,在開發的過程中,過度設計仍然是難以避免的情況,只有依靠經驗的積累和不斷的總結思考,慢慢調整和豐富自己的個人經驗了。
4. 單一職責原則與依賴註入
對於單例模式,我似乎也會遇到過度設計這種情況——每個對象的單例都需要再寫一個類去封裝,似乎也太麻煩了。
實際上這並非過度設計,因為這種設計是必要的,它能夠節省性能的開銷,但是對象的創建和管理依然是對開發者一個不可小覷的工作量。
此外,還需要考量的是,對於一個復雜的單例對象,它可能有很多的狀態和依賴,這意味著,單例類的職責很有可能很重,這在一定程度上違背了單一職責原則:
一個類只負責一個功能領域中的相應職責,或者可以定義為:就一個類而言,應該只有一個引起它變化的原因。
單一職責原則告訴我們:一個類不能太“累”! 一個類的職責越重(這往往從構造器所需要的依賴就能體現出來),它被復用的可能性就越小。
在了解了單例模式的優點和缺點後,我們可以有選擇的使用單例模式,對於依賴過於復雜的對象的單例,我們更需要仔細考量。
對於復雜的依賴管理,依賴註入庫(比如Dagger)是一個可以考慮的解決方案(慎重),對於單例模式的實現,你只需要在Module中對應的依賴Provider上添加一個@Singleton註解,編譯器會在編譯期間為您自動生成對應的單例模式代碼。
不能否認,這個工具需要相對較高的學習成本,但是學會了依賴註入工具並理解了IOC(控制反轉),DI(依賴註入)的思想之後,它將成為你開發過程中無往不勝的利器。
5.開閉原則
開閉原則:一個軟件應對擴展開放、對修改關閉,用head first中的話說就是:代碼應該如晚霞中 的蓮花一樣關閉(免於改變),如晨曦中的蓮花一樣開放(能夠擴展).
建造者模式(Builder)便是開閉原則的完全體現,它將對象的構建和調用隔離開來,不同的使用者都可以通過自由的構建對象,然後使用它。
6.小結
創建型模式是最容易入門的,因為該類型的模式,更經常暴露在開發者面前,但是它們並不簡單,我們除了知道這些模式的使用方式,更應該去思考什麽時候用,用哪個,甚至是組合使用它們——它們有些互斥,有些也可以互補,這需要我們去研究更經典的一些代碼,並自己作出嘗試。
不只是創建型,接下來的結構型和行為型的設計模式,本文也不會去一一闡述其目錄下所有的設計模式。
結構型模式
1.定義
首先闡述書中結構型模式的定義:
結構型模式涉及到如何組合類和對象以獲得更大的結構。結構型類模式采用繼承機制來組合接口或實現。
在學習之初,對我個人而言,閱讀《設計模式:可復用面向對象軟件的基礎》 的內容宛如誦讀天書,書中對每種設計模式都進行了詳細的講解,但是我看完之後,很快就忘掉了,亦或是對看起來非常相似的兩種設計模式感到疑惑——書中的講解細致入微,但是太抽象了。
最終(也就是現在),我個人對於結構型模式的理解是,通過將不同類或對象的組合,采用繼承或者組合接口,或者組合一些對象,以實現新的功能。
用一句話陳述,就是對不同職責的對象(以對象/抽象類/接口的形式)之間組合調度的實現方式。
2.並非所有對象的組合都是結構型模式
實際上,並非所有對對象的組合都屬於結構型模式,構型模式的意義在於,對一些對象的組合,以實現新功能的方式—— 通過運行時,通過改變組合的關系,這種靈活性產生不同的效果,這種機制,普通的對象組合是不可能實現的。
接下來我將通過闡述數種不同的結構型模式在實際開發中的應用,逐步加深對上文敘述的理解。
3.RecyclerView:適配器模式
RecyclerView是Android日常開發中實現列表的首選方案,站在我的角度來看,我還沒想明白一個問題,RecyclerView是如何實現列表的?
我可以回答說,通過實現RecyclerView.Adapter就能實現列表呀!
事實上,是這樣的,但是這引發了另外一個問題,Adapter和RecyclerView之間的關系是什麽,為啥實現了Adapter就能實現RecyclerView呢?
思考現實中的一個問題,我有一臺筆記本電腦,我的屋子裏也有一個電源,我如何給我的筆記本充電?
不假思索,我們用筆記本的充電器連接電源和筆記本就行了,實際上,充電器更官方的叫法應該叫做電源適配器(Adapter)。對於筆記本電腦和電源來講,它們並沒有直接的關系,但是通過Adapter適配器,它們就能產生新的功能——電源給筆記本充電。
RecyclerView和數據的展示也是一樣,數據對象和RecyclerView並沒有直接的關系,但是我如果想要將數據展示在RecyclerView上,通過給RecyclerView配置一個適配器(Adapter)以連接數據源,就可以了。
現在我們來看Adapter模式的定義:
使原本由於接口不兼容而不能一起工作的那些類可以一起工作。
現在我們理解了適配器模式的應用場景,但是我想拋出一個問題:
為啥我要實現一個Adapter,設計之初,為什麽不能直接設置RecyclerView呢?
比如說,我既然有了數據源,為什麽設計之初,不能讓RecyclerView通過這樣直接配置呢:
mRecyclerView.setDataAndShow(datas);
- 1
我的理解是,如果把RecyclerView比喻為屋子裏的電源插口,電源不知道它將要連接什麽設備(同樣,RecyclerView也不可能知道它要展示什麽樣的數據,怎麽展示),而不同的設備的接口也可能不一樣,但是只要為設備配置一個對應的適配器,兩個不相關的接口就能一起工作。
RecyclerView的設計者將實現對開發者隱藏,並通過Adapter對開發者暴露其接口,開發者通過配置數據源(設備)和對應的適配器(充電器),就能實現列表的展示(充電)。
4.Retrofit:外觀模式與動態代理
說到迪米特法則(也叫最少知識原則),這個應該很好理解,就是降低各模塊之間的耦合:
迪米特法則:一個軟件實體應當盡可能少地與其他實體發生作用。
我的學習過程中,讓我感受到設計模式的組合之美的第一個庫就是Retrofit,對於網絡請求,你只需要配置一個接口:
public interface BlogService {
@GET("blog/ www.taohuaqing178.com {id}")
Call<ResponseBody> getBlog(@Path(www.huayi1.cn/"id") int id);
}
// 使用方式
// 1.初始化配置Retrofit對象
Retrofit retrofit = new Retrofit.Builder(www.078881.cn)
.baseUrl("http://www.jyz521.com localhost:4567/ www.00534.cn ")
.addConverterFactory(www.dongfan178.com/ GsonConverterFactory.create())
.build();
// 2.實例化BlogService接口
BlogService service = retrofit.create(BlogService.class);
Retrofit的源碼中,通過組合,將各種設計模式應用在一起,構成了整個框架,保證了我們常說的高內聚,低耦合,堪稱設計模式學習案例的典範,如下圖(圖片參考感謝這篇文章):
在分析整個框架的時候,我們首先從API的使用方式入手,我們可以看到,在配置Retrofit的時候,庫采用了外觀模式作為Retrofit的門面。
有朋友說了,在我看來,Retrofit的初始化,不應該是Builder模式嗎,為什麽你說它是外觀模式呢?
我們首先看一下《設計模式:可復用面向對象軟件的基礎》一書對於外觀模式的定義:
為子系統中的一組接口提供一個一致的界面,外觀模式定義一個高層接口,這個接口使得這一子系統更容易使用。
我的解讀是,對於網絡請求庫的Retrofit,它內部有著很多不同的組件,包括數據的序列化,線程的調度,不同的適配器等,這一系列復雜的子系統,對於網絡請求來講,都是不可或缺的且關系復雜的,那麽,通過將它們都交給Retrofit對象去配置和調度(當然,Retrofit對象的創建是通過Builder模式實現的),對於API的調用者來說,使用配置起來簡單方便,這符合外觀模式 的定義。
簡單理解了外觀模式的思想,接下來我們來看一下動態代理,對於最初接觸Retrofit的我來說,我最難以理解的是我只配置了一個接口,Retrofit是如何幫我把Service對象創建出來的呢?
// 2.實例化BlogService接口
BlogService service = retrofit.create(BlogService.class);
- 1
- 2
實際上,並沒有BlogService這個對象的創建,service只不過是在jvm運行時動態生成的一個proxy對象,這個proxy對象的意義是:
為其他對象提供一種代理以控制對這個對象的訪問。
我想通過BlogService進行網絡請求,Retrofit就會通過動態代理實現一個proxy對象代理BlogService的行為,當我調用它的某個方法請求網絡時,實際上是這個proxy對象通過解析你的註解和方法的參數,通過一系列的邏輯包裝成一個網絡請求的OkHttpCall對象,並請求網絡。
現在我明白了,怪不得我無論怎麽給Service的接口和方法命名,Retrofit都會動態生成代理對象並在調用其方法時進行解析,對於復雜多變的網絡請求來講,這種實現的方式非常合適。
5.裏氏替換原則
在優秀的源碼中,我們經常可以看到,很多功能的實現,都是依賴其接口進行的,這裏我們首先要理解面向對象中最重要的基本原則之一裏氏替換原則:
任何基類可以出現的地方,子類一定可以出現。
裏氏代換原則是對開閉原則的補充。實現開閉原則的關鍵步驟就是抽象化。而基類與子類的繼承關系就是抽象化的具體實現,所以裏氏代換原則是對實現抽象化的具體步驟的規範。
向上轉型是Java的基礎,我們經常也用到,實際上,在進行設計的時候,盡量從抽象類繼承,而不是從具體類繼承。同時,保證在軟件系統中,把父類都替換成它的子類,程序的行為沒有變化,就足夠了。
6.小結
通過上述案例,我們簡單理解了幾種結構型設計模式的概念和思想,總結一下:
在解決了對象的創建問題之後,對象的組成以及對象之間的依賴關系就成了開發人員關註的焦點,因為如何設計對象的結構、繼承和依賴關系會影響到後續程序的維護性、代碼的健壯性、耦合性等。所以也有多種結構型模式可供開發人員選擇使用。
提高類之間的協作效率——行為型模式
1.定義
我們先看書中對行為型模式比較嚴謹的定義:
行為模式涉及到算法和對象間職責的分配,行為模式不僅描述對象或類的模式,還描述它們之間的通信模式。這些模式刻劃了在運行時難以跟蹤的復雜的控制流,將你的註意力從控制流轉移到對象間的聯系方式上來。
依然是有點難以理解,我們先舉兩個例子:
2.OkHttp:Intercepter和職責鏈模式
在 Okhttp 中, Intercepter就是典型的職責鏈模式的體現.它可以設置任意數量的Intercepter來對網絡請求及其響應做任何中間處理——設置緩存, Https的證書驗證, 統一對請求加密/防串改, 打印自定義Log, 過濾請求等。
new OkHttpClient.Builder()
.addNetworkInterceptor(interceptor1)
.addNetworkInterceptor(interceptor2)
.addNetworkInterceptor(interceptor3)
職責鏈模式的定義為:
讓多個對象都有機會處理請求,從而避免請求的發送者和接受者之間的耦合關系,將他們連成一條鏈,並沿著這條鏈傳遞該請求,直到有對象處理它為止。
以現實為例,職責鏈模式之一就是網絡連接,七層或五層的網絡連接模型如下:
-
網絡請求發出,經過應用層->傳輸層->網絡層->連接層->物理層
-
收到響應後,物理層->連接層->網絡層->傳輸層->應用層
在請求經過各層時,由每層輪流處理.每層都可以對請求或響應進行處理.並可以中斷鏈接,以自身為終點返回響應。
3.RxJava:觀察者模式
Android開發中,點擊事件的監聽是很經典觀察者模式的體現:
button.setOnClickListener(v -> {
// do somethin
對設置OnClickListener來說,View是被觀察者,OnClickListener是觀察者,兩者通過setOnClickListener()方法達成註冊(訂閱)關系。訂閱之後,當用戶點擊按鈕,View就會將點擊事件發送給已經註冊的 OnClickListener。
同樣,對於可以和Retrofit配套的RxJava來講,它是也通過觀察者模式來實現的。
// 被觀察者
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!");
}
};
// 執行訂閱關系
observable.subscribe(observer);
RxJava強大的異步處理,將數據的創建和接收分成了兩部分,對於觀察者來說,它不關心數據什麽時候發射的,怎麽發射的,它只關心,當觀察到到最新數據時,怎樣進行對應的處理。
我們知道了觀察者模式的這種方式,我們更需要去深入思考對於觀察者模式使用前後,對我們代碼設計系統的影響——它的好處是什麽?
最直接的好處是,被觀察者並不知道觀察者的詳細實現。
就像我剛才所說的,被觀察者只負責發射事件,對於事件如何處理,它並不關心,這意味著被觀察者和觀察者之間並不是緊密耦合的,它們可以處於一個系統中的不同抽象層次。
不同抽象層次這句話本身就有點抽象,我們以Button的點擊事件為例,對於Button來講,它是一個庫的工具,它應該屬於項目中底層組件,而對於我們某個Activity的某個點擊事件來講,它是屬於靠頂部業務層的代碼,可以說,Button和點擊事件是不在一個抽象層次,較低層次的Button可以將點擊事件發送給較高層次的事件監聽器並通知它。
而如果不采用這種方式,觀察者和被觀察者就必須混在一起,這樣對象就會橫貫項目的2個層次(違反了層次性),或者必須放在這兩層中的某一層中(可能會損害層次抽象)。
將底層組件按鈕被點擊後行為,抽象出來交給較高層級去實現,了解了這種方式的好處,依賴倒置原則就不難理解了。
4.依賴倒置原則
現在我們來了解一下依賴倒置原則:
抽象不應該依賴於細節,細節應當依賴於抽象。換言之,要針對接口編程,而非針對實現編程。
它的原則是:
- 1.高層模塊不應該依賴於低層模塊,兩個都應該依賴於抽象。
- 2.抽象不應該依賴細節,細節應該依賴於抽象。
在java中,抽象指的是接口或者抽象類,細節就是具體的實現類,使用接口或者抽象類的目的是制定好規範,而不去涉及任何具體的操作,把展現細節的任務交給他們的實現類去完成。
了解了依賴倒置原則,我們再接再厲,學習最後一個設計模式的基本原則:
5.接口隔離原則
接口隔離原則:客戶端不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上。
這個應該是最好理解的原則了,它的意義就是:使用多個專門的接口比使用單一的總接口要好。
這很好理解,對於鳥的實現(Bird),我們可以定義兩個功能接口,分別是Fly和Eat,我們可以讓Bird分別實現這兩個接口——如果我們還有一個Dog,那麽對於Eat接口,可以復用,但是如果只有一個接口(包含Fly和Eat兩個功能),對於Dog來說,它是不會飛(Fly)的,那麽就需要針對Dog再聲明一個新的接口,這是沒有必要的設計。
6.小結
在對象的結構和對象的創建問題都解決了之後,就剩下對象的行為問題了,如果對象的行為設計的好,那麽對象的行為就會更清晰,它們之間的協作效率就會提高。
現在我們再看上文中對行為型模式比較嚴謹的定義,相信大家能夠理解一些了:
行為模式涉及到算法和對象間職責的分配,行為模式不僅描述對象或類的模式,還描述它們之間的通信模式。這些模式刻劃了在運行時難以跟蹤的復雜的控制流,將你的註意力從控制流轉移到對象間的聯系方式上來。
喘口氣
關於設計模式相關的講解內容,到此基本就告一段落了。
等等…
我一個設計模式都沒學會,你TM告訴我你講完了?
一無所獲?
本文並沒有去通過代碼簡單描述各個設計模式的實現方式,於我而言毫無意義,設計思想是通過不斷思考,理解並在親身嘗試中學會的,短時間快速大量閱讀學習的方式效果甚微,即使學會了,如何將sample中的設計思想,轉換為實際產品中復雜的業務設計,這也是一個非常大的難題。
在這裏,我引用《倚天屠龍記》中我最喜歡的經典片段, 就是張三豐在武當山當著敵人的面教張無忌太極劍那段。
“只聽張三豐問道:‘孩兒,你看清楚了沒有?’張無忌道:‘看清楚了。’張三豐道: ‘都記得了沒有?’張無忌道:‘已忘記了一小半。’張三豐道:‘好,那也難為了你。你自己去想想罷。’張無忌低頭默想。過了一會,張三豐問道:‘現下怎樣了?’張無忌道: ‘已忘記了一大半。’
周顛失聲叫道:‘糟糕!越來越忘記得多了。張真人,你這路劍法很是深奧,看一遍怎能記得?請你再使一遍給我們教主瞧瞧罷。’
張三豐微笑道:‘好,我再使一遍。’提劍出招,演將起來。眾人只看了數招,心下大奇,原來第二次所使,和第一次使的竟然沒一招相同。周顛叫道:‘糟糕,糟糕!這可更加叫人胡塗啦。’張三豐畫劍成圈,問道:‘孩兒,怎樣啦?’張無忌道:‘還有三招沒忘記。’張三豐點點頭,收劍歸座。
張無忌在殿上緩緩踱了一個圈子,沈思半晌,又緩緩踱了半個圈子,擡起頭來,滿臉喜色,叫道:‘這我可全忘了,忘得幹幹凈凈的了。’張三豐道:‘不壞不壞!忘得真快,你這就請八臂神劍指教罷!’”
總結
關於設計模式,我的理解是,不要拘泥於其概念,只有深刻理解了其設計的思想,理解之後,親自去嘗試使用它,在使用的過程中加深對這種思想的理解,我想比通過書籍或者博客一個一個的去學,效果要更好。
我學習它們的方式是,學習一些優秀開源庫的源碼,思考為什麽這裏使用這些設計模式,之後再參考《設計模式》一書的相關概念,最後自己去嘗試並加深理解。
於我而言,這種方式的短板在於剛開始的時候,可能需要更多的時間去學習,很多庫的源碼在初接觸時,並非容易理解,但是好處是,當學會之後,這種思想就很難再從你的記憶中跑掉了,而且,在寫代碼時,會下意識嘗試使用這些模式(當然,更多情況是打開2個窗口,一邊參考源碼,一邊學習將其融入到自己的代碼中)。
設計模式相對於分類和概念正如太極拳(劍),它更是一種思想,一種應對變化的解決思想——我不認為本文自己的學習方式和心得,能夠適合每一個正在閱讀本文的讀者,但是如果本文對您對技術成長的道路上有一些幫助,對我而言這就是值得的。
如何通俗理解設計模式及其思想