如何通俗理解設計模式及其思想?

玉剛說發表於2018-07-05

本文由玉剛說寫作平臺提供寫作贊助

原作者:卻把清梅嗅

版權宣告:本文版權歸微信公眾號玉剛說所有,未經許可,不得以任何形式轉載

術與道

資料結構,演算法,設計模式被認為是程式設計師必備技能的三叉戟,如果說程式語言的語法特性和業務編碼能力是【術】,那麼這三者可以稱得上是【道】——【術】可以讓你在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個基本原則(這裡先列出來,接下來會參考案例一個個解釋):

  • 單一職責原則(Single Responsibility Principle)
  • 里氏代換原則(Liskov Substitution Principle)
  • 依賴倒轉原則(Dependence Inversion Principle)
  • 介面隔離原則(Interface Segregation Principle)
  • 迪米特法則,又稱最少知道原則(Demeter Principle)
  • 開閉原則(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) {
            //....
       }
   })
   .create()
   .show();
複製程式碼

此外,稍微細心的同學會發現,其實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);
複製程式碼

我的理解是,如果把RecyclerView比喻為屋子裡的電源插口,電源不知道它將要連線什麼裝置(同樣,RecyclerView也不可能知道它要展示什麼樣的資料,怎麼展示),而不同的裝置的介面也可能不一樣,但是隻要為裝置配置一個對應的介面卡,兩個不相關的介面就能一起工作。

RecyclerView的設計者將實現對開發者隱藏,並通過Adapter對開發者暴露其介面,開發者通過配置資料來源(裝置)和對應的介面卡(充電器),就能實現列表的展示(充電)。

4.Retrofit:外觀模式與動態代理

說到迪米特法則(也叫最少知識原則),這個應該很好理解,就是降低各模組之間的耦合:

迪米特法則:一個軟體實體應當儘可能少地與其他實體發生作用。

我的學習過程中,讓我感受到設計模式的組合之美的第一個庫就是Retrofit,對於網路請求,你只需要配置一個介面:

public interface BlogService {

    @GET("blog/{id}")
    Call<ResponseBody> getBlog(@Path("id") int id);
}

// 使用方式
// 1.初始化配置Retrofit物件
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://localhost:4567/")
        .addConverterFactory(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);
複製程式碼

實際上,並沒有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 something
})
複製程式碼

對設定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個視窗,一邊參考原始碼,一邊學習將其融入到自己的程式碼中)。

設計模式相對於分類和概念正如太極拳(劍),它更是一種思想,一種應對變化的解決思想——我不認為本文自己的學習方式和心得,能夠適合每一個正在閱讀本文的讀者,但是如果本文對您對技術成長的道路上有一些幫助,對我而言這就是值得的。

參考

1.菜鳥教程——設計模式:
http://www.runoob.com/design-pattern/design-pattern-tutorial.html

2.《設計模式:可複用物件導向軟體的基礎》
伽瑪等著;李英軍等譯,機械工業出版社,2015年12月第一版37次印刷

3.Retrofit分析-漂亮的解耦套路: https://blog.piasy.com/2016/06/25/Understand-Retrofit/

4.建立型模式、結構型模式和行為型模式之間的關係 https://blog.csdn.net/qq_34583891/article/details/70853637

如何通俗理解設計模式及其思想?
歡迎關注我的微信公眾號,接收第一手技術乾貨

相關文章