用Swift整理GOF設計模式(一)--掃盲設計模式

一、什么是設計模式

"每一個模式描述了一個在我們周圍不斷重復發生的問題以及該問題的解決方案的核心.這樣,你就能一次又一次地使用該方案而不必做重復的勞動". ---Christopher Alexander

我想告訴大家的是:
能看懂設計模式的代碼,你往往只是懂了皮毛,設計模式真正教給你的是,告訴你的是什么是設計原則,針對哪種變化、哪種場景使用哪種設計模式。
現實中的場景不會讓你在程序設計之初,一上來便套用設計模式,這往往十分不靠譜,更為實際的做法是,"Refactoring to Patterns",結合你身邊的代碼,使用設計模式來重構代碼。

二.分清設計模式與架構模式

剛開始接觸編程的新人往往分不清什么是設計模式,什么是架構模式。甚至只知道架構模式,而不是設計模式,這里羅列出從低到高的三種關系。
1.設計習語Design Idioms
Design Idioms描述與特定編程語言相關的底層模式、技巧、慣用法.
(舉個栗子來說的話,就像OC中的block,Swift中的函數編程、閉包、guard,不一一列舉)

2.設計模式Design Patterns
Design Patterns主要描述的是"類與相互通信的對象之間的組織關系,包括它們的角色、職責、協作方式等方面"
(如Delegate)

3.架構模式Architectural Patterns
Architectural Patterns描述系統中與基本結構組織關系密切的高層模式,包括子系統劃分,職責,以及如何組織它們之間的關系規則
(如MVVM、Redux、VIPPER、響應式Rx等等)

三、什么是GOF

設計模式的經典名著——Design Patterns: Elements ofReusable Object-Oriented Software,中譯本名為《設計模式——可復用面向對象軟件的基礎》的四位作者Erich Gamma、Richard Helm、Ralph Johnson,以及John Vlissides,這四人常被稱為Gang of Four,即四人組,簡稱GoF。

Design Patterns: Elements ofReusable Object-Oriented Software

該書描繪了23種經典的設計模式,創立了模式在軟件設計中的地位,通常所說的設計模式隱含地表示"面向對象設計模式".但不并表示就是等于"面向對象設計模式"

四、軟件設計的復雜和解決途徑

伴隨著下面4個不可避免的變化(客戶需求的變化、技術平臺的變化 、開發團隊的變化、市場環境的變化)
那么我們又該如何解決復雜性?
1.分解:人們面對復雜性有一個常見的做法:即分而治之,將大問題分解為多個小問題,將復雜的問題分解為多個簡單的問題
2.抽象:更高層次來講,人們處理復雜性有一個通用的技術,即抽象.由于不能掌握全部復雜的對象,我們選擇忽視它的非本質細節,而去處理泛化和理想化了的對象

五、面向對象設計原則

設計模式的原則才是最重要的,而不像算法,可以去套用,衡量一個程序的好壞,需要我們來對照這些原則的尺子去一一丈量。
變化是復用的天敵。而設計模式的存在是抵御變化,但并不意味沒有變化,而是將變化的范圍逐步縮小。
1、依賴倒置原則(DIP)

  • 高層模塊(穩定)不應該依賴于低層模塊(變化),二者都依賴于抽象(穩定)
  • 抽象(穩定)不應該依賴于實現細節(變化),實現細節應該依賴于抽象(穩定)

下面為代碼示例:
我們沒有人是生而知之的,在了解是什么是抽象類之前,我們一定都寫過這樣的代碼:

  class DrawingBoard{//繪畫板,代表高層模塊
        var lineArray:Array<Line>?
        var rectArray:Array<Rect>?
        func onPaint(){
            for lineInstance in lineArray{
                 event.Graphics.DrawLine(Pens.Red,
                  lineInstance.leftUp,
                  lineInstance.width,
                  lineInstance.height)
             }
            for rectInstance in rectArray{
                 event.Graphics.DrawRect(Pens.Red,
                  rectInstance.leftUp,
                  rectInstance.width,
                  rectInstance.height)
             }
         }
       
   }
  class Line{//底層模塊(代表容易變化的模塊)
       func Draw(){ ... }
   }
  class Rect{//底層模塊(代表容易變化的模塊)
        func Draw(){ ... }
   }      

而這個設計原則告訴我們應該像這樣去思考:

   class DrawingBoard{//繪畫板,代表高層模塊
        var shapeArray:Array<Shape>?
        func onPaint(){
             for shape in shapeArray{
                  shape.Draw();
             }
         }
   }
  protocol Shape{//抽象接口,同時也是一種穩定的模塊(高層和低層都依賴抽象類)
        func Draw(){  }
   }
  class Line:Shape{//底層模塊(代表容易變化的模塊)
        override func Draw() {  }//實現細節應該依賴于抽象
   }
  class Rect:Shape{//底層模塊(代表容易變化的模塊)
        override func Draw() {  }//實現細節應該依賴于抽象
   }      

結構就變成了這樣,看看現在是不是這樣的規則:
高層模塊(穩定)不應該依賴于低層模塊(變化),二者都依賴于抽象(穩定)
抽象(穩定)不應該依賴于實現細節(變化),實現細節應該依賴于抽象(穩定)

BB615F2C-4948-4E59-9271-4C0B51B5848E.png

2.開放封閉原則(OCP)

  • 對擴展開放,對更改封閉.
  • 類模塊應該是可擴展的,但是不可修改.

假如我們來一個新的需求時,如果不使用設計模式,我們經常會在原有代碼結構上進行更改。根據這個原則,我們應該避免這種更改,而選擇去擴展。
因為更改的代價往往是十分大的,

class DrawingBoard{
    var lineArray:Array<Line>?
    var rectArray:Array<Rect>?
    
    //新的改變需求
    var circleArray:Array<Circle>?

    func onPaint(event:PaintEventArgs){
        //舊代碼      
        for lineInstance in lineArray{
           //同下
        }    
        for rectInstance in rectArray{
            //同下
        }    
        //新代碼
        for circleInstance in circleArray{
             event.Graphics.DrawCircle(Pens.Red,
                      circleInstance.leftUp,
                      circleInstance.width,
                      circleInstance.height)
        }        
    }
   
}
class Line{//底層模塊(代表容易變化的模塊)
    //...
}
class Rect{//底層模塊(代表容易變化的模塊)
    //...
}
class Circle{
}

這種代碼就違反了開放封閉原則,它是在改變代碼,這就意味著這塊代碼需要重新編譯、重新測試、重新部署,改變的代價十分高昂。
我們依舊像之前那樣,重新修改代碼:

class DrawingBoard{//繪畫板,代表高層模塊
        var shapeArray:Array<Shape>?
        func onPaint(){
             for shape in shapeArray{
                  shape.Draw();
             }
         }
   }
  protocol Shape{//抽象接口,同時也是一種穩定的模塊(高層和低層都依賴抽象類)
        func Draw(){  }
   }
  class Line:Shape{//底層模塊(代表容易變化的模塊)
        override func Draw() {  }//實現細節應該依賴于抽象
   }
  class Rect:Shape{//底層模塊(代表容易變化的模塊)
        override func Draw() {  }//實現細節應該依賴于抽象
   }  
  class Circle:Shape{//底層模塊(代表容易變化的模塊)
        override func Draw() {  }//實現細節應該依賴于抽象
   }  

第二種方法明顯就是一種以擴展的方式應對新的需求,這就是來自面向對象的智慧。

9155AFEB-E0C8-4CC0-B11A-225AA1B5D46A.png

上圖紅色的部分代表修改&新增。

3.接口隔離原則(ISP)

  • 不應該強迫客戶程序依賴它們不用的方法
  • 接口應該小而完備

不要去暴露不該暴露的接口,需要我們去考慮什么使用private,internal,public。如果庫開發程序員無節制的public 方法給iOS應用開發程序員,iOS應用開發程序員就會和一些不應該public的接口產生依賴,這樣你的接口就都需要保持穩定。
所以接口應該小而完備。

4.優先使用對象組合,而不是類繼承

  • 類繼承通常為"白箱復用",對象組合通常為"黑箱復用"
  • 繼承在某種程度上破壞了封裝性,子類父類耦合度高。
  • 而對象組合則只要求被組合的對象具有良好定義的接口,耦合度低

許多初學面向對象的程序員都非常喜歡使用繼承。因為面向對象中的繼承更符合我們直觀的世界觀。
就像相較于函數式編程,我們更加會適應命令式編程,因為函數式編程的數學思想不容易被接受,使用命令式編程更明顯地看到如何將真實世界中的對象和程序語言中的對象一一對應。
而關于組合優于繼承的例子,我在裝飾模式一文已經提及。

5.單一職責原則(SRP)

  • 一個類應該僅有一個引起它變化的原因
  • 變化的方向隱含著類的責任

如果我們一個類充滿了幾十個方法和成員時,這明顯是不正常的,這就代表隱含了多個責任,就像iOS開發中如果將ViewController和View混淆在一起,這明顯是不對的,當隱含多個責任時,很明顯會出問題.
之后寫的文章 橋模式裝飾模式就會遇到類的責任問題,新手開發者如果輕視責任的問題,甚至會造成整個程序的設計出現問題。

6.Liskov替換原則(LSP)

  • 子類必須能夠替換他們的基類(IS-A)
  • 繼承表達類型抽象

一般而言,這個原則看起來似乎天經地義,子類替換父類似乎是理所當然的,的確如此,但是不排除有以下情況的出現:

class 樂器{
    func 奏樂() -> Void {     
    }
    func 調音() -> Void {       
    }
}
class 武器:樂器{
    override func 奏樂() -> Void {
        fatalError("無法奏樂")
    }
    override func 調音() -> Void {
        fatalError("無法調音")
    }
}

這個設計看上去似乎十分可笑,但是很多程序員在現實設計時,會發現子類有時候確實就是不應該使用父類的方法,于是直接拋出異常。例子看上去很傻瓜,但當真實投入實踐,有時候我們就會犯糊涂。
這顯然就違背了我們的原則,證明了武器這個類壓根就不應該設計為子類。

7.封裝變化點

  • 使用封裝來創建對象之間的分界層,讓設計者可以在一側進行修改,而不會對另外一側產生不良的影響

這里依舊拿庫開發程序員舉例,如果庫開發程序員不封裝變化點,對外接口不是穩定的,而是變化的,那么每次修改,都會導致iOS開發程序員同時進行修改。
這里我拿Swift中的官方代碼舉例:

public func assert(
  _ condition: @autoclosure () -> Bool,
  _ message: @autoclosure () -> String = String(),
  file: StaticString = #file, line: UInt = #line
) {
      _assertionFailed("assertion failed", message(), file, line,
        flags: _fatalErrorFlags())
    }
}

_assertionFailed可以是變化的,而assert是穩定的

8.面向接口編程,而不是針對實現編程

  • 客戶程序無需獲知對象的具體類型,只需知道對象所具有的接口。
  • 減少系統中各部分的依賴關系,從而實現"高內聚、松耦合"的類型設計方案。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容

  • 設計模式匯總 一、基礎知識 1. 設計模式概述 定義:設計模式(Design Pattern)是一套被反復使用、多...
    MinoyJet閱讀 3,955評論 1 15
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,776評論 18 139
  • 設計模式基本原則 開放-封閉原則(OCP),是說軟件實體(類、模塊、函數等等)應該可以拓展,但是不可修改。開-閉原...
    西山薄涼閱讀 3,831評論 3 14
  • 小時候的冬至是一碗濃濃的疙瘩湯,放學以后,踏著厚厚的雪地往家趕,或鵝毛大雪,或窸窣小雪,總之,像約定好似的,冬至這...
    Sophie朵兒閱讀 1,011評論 2 7
  • 昨夜飲醉亭風, 解衣欲睡還濃。 本以潮聲漸起, 卻是玉嶺橫城。
    亂棋閱讀 168評論 0 0