定義
設(shè)計模式(Design Pattern)是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類的、代碼設(shè)計經(jīng)驗的總結(jié)。 使用設(shè)計模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。
設(shè)計模式的六大原則
單一職責(zé)原則(Single responsibility principle)
應(yīng)該有且僅有一個原因引起類的變更(There should never be more than one reason for a class to change)
單一職責(zé)原則為我們提供了一個編寫程序的準(zhǔn)則,要求我們在編寫類,抽象類,接口時,要使其功能職責(zé)單一純碎,將導(dǎo)致其變更的因素縮減到最少。開閉原則(Open Close Principle)
開閉原則要求我們要盡可能的通過保持原有代碼不變添加新代碼而不是通過修改已有的代碼來實現(xiàn)軟件產(chǎn)品的變化
說的通俗一點就是,已經(jīng)開發(fā)好的軟件實體(如類、模塊、函數(shù)),在升級迭代引入新功能時,不應(yīng)該修改已有的代碼,而是在已有代碼的基礎(chǔ)上,添加新代碼來實現(xiàn)。里氏代換原則(Liskov Substitution Principle)
任何使用基類的地方都可以在不了解其子類具體實現(xiàn)的情況下,無條件地使用其子類替換,而不會產(chǎn)生任何錯誤或異常。
這要求子類可以拓展父類的功能,但不能改變父類原有的功能。包含以下4個方面:
1、子類必須實現(xiàn)父類的抽象方法,但是不能重寫父類的非抽象方法。
2、子類可以增加自己特有的方法
3、當(dāng)子類重載父類的方法時,方法的形參要比父類更寬松
4、當(dāng)子類重載父類的方法時,方法的返回值要比父類更嚴(yán)格依賴倒轉(zhuǎn)原則(Dependence Inversion Principle)
高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴其抽象;抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象。
這個原則是開閉原則的基礎(chǔ),具體內(nèi)容:針對接口編程,依賴于抽象而不依賴于具體。接口隔離原則(Interface Segregation Principle)
客戶端不應(yīng)該依賴它不需要的接口(Clients should not be forced to depend upon interfaces that they don't use)
類間的依賴關(guān)系應(yīng)該建立在最小的接口上(The dependency of one class to another one should depend on the smallest possible interface)
接口隔離原則要求我們在設(shè)計接口時,要使用多個專門的接口不要使用單一的龐大臃腫的總接口,一個類對另一個類的依賴性應(yīng)該建立在最小的接口上。要做到接口與角色一一對應(yīng),不應(yīng)該讓一個接口承擔(dān)多個角色,也不應(yīng)該讓一個角色由多個接口承擔(dān)。這樣設(shè)計的接口在應(yīng)對未來變更時,會更具有靈活性和可拓展性。迪米特法則,又稱最少知道原則(Demeter Principle)
最少知道原則是指:一個實體應(yīng)當(dāng)盡量少地與其他實體之間發(fā)生相互作用,使得系統(tǒng)功能模塊相對獨立。
通俗的講一 個類對自己需要耦合或者調(diào)用的類應(yīng)該知道的最少,你類內(nèi)部是怎么復(fù)雜、怎么的糾纏不清都和我沒關(guān)系, 那是你的類內(nèi)部的事情,我就知道你提供的這么多 public 方法,我就調(diào)用這個
這些東西都是一下概念上的描述,理解起來比較困難,小伙伴們可以看看下面的文章,有助于大家更好的理解這些原則。
設(shè)計模式原則之接口隔離原則
設(shè)計模式心法
偶my耶-設(shè)計模式
設(shè)計模式的類型
根據(jù)設(shè)計模式的參考書 Design Patterns - Elements of Reusable Object-Oriented Software(中文譯名:設(shè)計模式 - 可復(fù)用的面向?qū)ο筌浖兀?中所提到的,總共有 23 種設(shè)計模式。這些模式可以分為三大類:創(chuàng)建型模式(Creational Patterns)、結(jié)構(gòu)型模式(Structural Patterns)、行為型模式(Behavioral Patterns)
創(chuàng)建型模式:
這些設(shè)計模式提供了一種在創(chuàng)建對象的同時隱藏創(chuàng)建邏輯的方式,而不是使用 new 運算符直接實例化對象。這使得程序在判斷針對某個給定實例需要創(chuàng)建哪些對象時更加靈活。
主要用于創(chuàng)建對象。
結(jié)構(gòu)型模式:
這些設(shè)計模式關(guān)注類和對象的組合。繼承的概念被用來組合接口和定義組合對象獲得新功能的方式。
主要用于處理類或?qū)ο蟮慕M合。
行為型模式
這些設(shè)計模式特別關(guān)注對象之間的通信。
主要用于描述對類或?qū)ο笤鯓咏换ズ驮鯓臃峙渎氊?zé)。
常用的設(shè)計模式
本文將簡單介紹在iOS開發(fā)中幾種常用的設(shè)計模式:
創(chuàng)建型模式
工廠模式
在工廠方法模式中,工廠父類負(fù)責(zé)定義創(chuàng)建產(chǎn)品對象的公共接口,而工廠子類則負(fù)責(zé)生成具體的產(chǎn)品對象,這樣做的目的是將產(chǎn)品類的實例化操作延遲到工廠子類中完成,即通過工廠子類來確定究竟應(yīng)該實例化哪一個具體產(chǎn)品類。
比如存在如下需求:
系統(tǒng)需要提供兩個畫筆(鉛筆、鋼筆),根據(jù)業(yè)務(wù)需要使用其中一種來繪制頁面。
根據(jù)上述需求,我們可以創(chuàng)建兩個類 Pencil 和 FountainPen,然后他們倆各自有自己的方法 draw。
到這里,我們的工作其實已經(jīng)結(jié)束,開發(fā)者只需在使用的地方進(jìn)行判斷,初始化相應(yīng)的對象來調(diào)用 draw 即可。
這樣的話會存在什么樣的問題呢,比如項目中多個對方存在對這個兩個對象的初始化,假如有一天,對于這兩個對象的創(chuàng)建存在一些邏輯上的限制,就需要修改多個地方。利用工廠模式對將創(chuàng)建對象都放在一個地方的話,將來進(jìn)行修改就比較方便。
在工廠模式中,我們在創(chuàng)建對象時不會對客戶端暴露創(chuàng)建邏輯,并且是通過使用一個共同的接口來指向新創(chuàng)建的對象。
那么對于上述需求該怎么設(shè)計呢:
首先 Pencil 和 FountainPen 肯定是實現(xiàn)了同一個協(xié)議(PenProtocol 協(xié)議提供方法 func draw())的,然后我們?yōu)?Pencil 和 FountainPen 分別創(chuàng)建一個工廠類 PencilFactory 和 FountainPenFactory,這兩個工廠類也是實現(xiàn)了同一個協(xié)議 (PenFactoryProtocol 協(xié)議提供方法 static func createPen() -> PenProtocol),這樣我們就可以在需要的時候,來使用相應(yīng)的工廠,創(chuàng)建出我們需要的對象來進(jìn)行繪制了。
上面一堆文字不太好看懂,來張圖看看:

圖也看不懂,沒關(guān)系,上代碼:
兩個協(xié)議
protocol PenProtocol {
//繪制的方法
func draw()
}
protocol PenFactoryProtocol {
//生產(chǎn)筆的方法
static func createPen() -> PenProtocol
}
兩種產(chǎn)品
import UIKit
class Pencil: NSObject {
}
extension Pencil:PenProtocol{
func draw() {
print("Pencil" + "繪制")
}
}
import UIKit
class FountainPen: NSObject {
}
extension FountainPen:PenProtocol{
func draw() {
print("FountainPen" + "繪制")
}
}
兩個產(chǎn)品對應(yīng)兩個工廠
import UIKit
class PencilFactory: NSObject {
}
extension PencilFactory:PenFactoryProtocol{
class func createPen() -> PenProtocol {
return Pencil()
}
}
import UIKit
class FountainPenFactory: NSObject {
}
extension FountainPenFactory:PenFactoryProtocol{
class func createPen() -> PenProtocol {
return FountainPen()
}
}
調(diào)用
print("工廠模式")
//工廠模式使用(需要什么對象,就用對應(yīng)的工廠類來創(chuàng)造)
PencilFactory.createPen().draw()
//工廠模式使用
FountainPenFactory.createPen().draw()
工廠模式的結(jié)構(gòu)就是上面這種的,總體思想就是一個工廠生產(chǎn)一個類的對象。
下面是一個在iOS中使用的實例:
iOS - TableViewCell(CollectionViewCell)的工廠設(shè)計模式
在創(chuàng)建型模式模式中,還存在兩個和工廠模式類似的模式,就是 簡單工廠模式、抽象工廠模式。本文就不做介紹了,以后有時間可以另外補(bǔ)充文章。
單例模式
單例模式(Singleton Pattern):單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例,這個類稱為單例類,它提供全局訪問的方法。
單例模式的要點有三個:
一是某個類只能有一個實例;
二是它必須自行創(chuàng)建這個實例;
三是它必須自行向整個系統(tǒng)提供這個實例。
使用場景:
- 需要頻繁的進(jìn)行創(chuàng)建和銷毀的對象;
- 創(chuàng)建對象時耗時過多或耗費資源過多,但又經(jīng)常用到的對象;
- 工具類對象;
- 頻繁訪問數(shù)據(jù)庫或文件的對象。
類圖 :

代碼:
import UIKit
class SingleObject: NSObject {
@objc static let shared = SingleObject.init()
private override init() {
super.init()
}
}
調(diào)用
print("單例模式")
print(SingleObject.shared)
print(SingleObject.shared)
這里給到的是 swift 的寫法,比較簡單粗暴,關(guān)于單例在 oc 中的實現(xiàn),可以看下面的文章。
相關(guān)文章
iOS 單例模式
iOS-單例模式寫一次就夠了
結(jié)構(gòu)型模式
代理模式
這個模式不說啦,我是越研究越懵逼。
主要在糾結(jié) iOS 中的代理、委托 到底是不是基于這種設(shè)計模式的。
真的整不明白啦,就不多說啦,免得誤導(dǎo)大家,等哪天能力提升,搞清楚啦,再來補(bǔ)充這里一塊兒。
貼幾個文章大家有興趣的可以看看。
設(shè)計模式---代理模式
【java設(shè)計模式】(3)---代理模式(案例解析)
行為型模式
觀察者模式
當(dāng)對象間存在一對多關(guān)系時,則使用觀察者模式(Observer Pattern)。比如,當(dāng)一個對象被修改時,則會自動通知它的依賴對象。觀察者模式屬于行為型模式。
意圖:定義對象間的一種一對多的依賴關(guān)系,當(dāng)一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。
主要解決:一個對象狀態(tài)改變給其他對象通知的問題,而且要考慮到易用和低耦合,保證高度的協(xié)作。
何時使用:一個對象(目標(biāo)對象)的狀態(tài)發(fā)生改變,所有的依賴對象(觀察者對象)都將得到通知,進(jìn)行廣播通知。
舉例說明,在一場短跑比賽上,裁判員的槍聲一響,所有運動員都要極速沖出去。在這里運動員就是觀察者,裁判員就是目標(biāo)對象。
使用場景:
- 對一個對象狀態(tài)的更新,需要其他對象同步更新,而且其他對象的數(shù)量動態(tài)可變。
- 對象僅需要將自己的更新通知給其他對象而不需要知道其他對象的細(xì)節(jié)。
現(xiàn)在就使用上面提到的運動員和裁判員作為業(yè)務(wù)需求,看看代碼應(yīng)該怎么實現(xiàn)。
類圖 :

代碼:
首先需要一個抽象的觀察者,這類觀察者就觀察發(fā)號槍的槍聲
protocol Observer {
//觀察 發(fā)號槍 槍響
func acceptGunshot()
}
具體的實例(運動員),來擴(kuò)展 Observer 類,從而在觀察到 acceptGunshot 觸發(fā)時,就趕緊沖出去。
import UIKit
class Sportsman: NSObject {
//跑步
fileprivate func run(){
print("\(self):快跑")
}
}
extension Sportsman: Observer{
func acceptGunshot() {
run()
}
}
被觀察者(裁判員),需要給外部提供一個成為自己觀察者的方法,然后在特定的時間點,來通知觀察者。
import UIKit
class Judgment: NSObject {
//觀察者
fileprivate var observers:[Observer] = []
//添加觀察者
public func addObserver(observer:Observer){
observers.append(observer)
}
//開槍
public func gunshot(){
print("biubiubiu")
//通知所有觀察者
notifyAllObservers()
}
fileprivate func notifyAllObservers(){
for item in observers {
item.acceptGunshot()
}
}
}
調(diào)用
print("觀察者模式")
//裁判就位
let judgment:Judgment = Judgment.init()
//三個運動員就位
let sportsman1:Sportsman = Sportsman()
let sportsman2:Sportsman = Sportsman()
let sportsman3:Sportsman = Sportsman()
//都要聽裁判的發(fā)令槍
judgment.addObserver(observer: sportsman1)
judgment.addObserver(observer: sportsman2)
judgment.addObserver(observer: sportsman3)
//開炮
judgment.gunshot()
觀察者模式在iOS中經(jīng)典的應(yīng)用場景就是 Notification、KVO。
有興趣的同學(xué)可以看看下面這篇文章:
iOS Notification實現(xiàn)原理
策略模式
在策略模式(Strategy Pattern)中,一個類的行為或其算法可以在運行時更改。這種類型的設(shè)計模式屬于行為型模式。
意圖:定義一系列的算法,把它們一個個封裝起來, 并且使它們可相互替換。
主要解決:在有多種算法相似的情況下,使用 if...else 所帶來的復(fù)雜和難以維護(hù)。
使用場景: 1、如果在一個系統(tǒng)里面有許多類,它們之間的區(qū)別僅在于它們的行為,那么使用策略模式可以動態(tài)地讓一個對象在許多行為中選擇一種行為。 2、一個系統(tǒng)需要動態(tài)地在幾種算法中選擇一種。 3、如果一個對象有很多的行為,如果不用恰當(dāng)?shù)哪J?,這些行為就只好使用多重的條件選擇語句來實現(xiàn)。
現(xiàn)在產(chǎn)品又來了一個這樣的需求,要設(shè)計計算類,這個類需要支持加法運算和減法運算。多數(shù)人的處理方案應(yīng)該就是以下兩種:
1、寫一個運算的方法,通過傳遞參數(shù)進(jìn)行不同的運算。
2、寫兩個方法,來處理不同的運算。
這樣的話如果哪天需求變更,比如要添加一個乘法運算和除法運算,這時候用上面的方法就要修改原有的類,這樣就不太優(yōu)雅。使用策略模式改怎么處理呢?
類圖:

代碼:
為了擴(kuò)展,肯定有個策略的接口,方便將來策略的具體實現(xiàn)。
protocol OperationStrategy {
/// 運算處理
///
/// - Parameters:
/// - num1: 數(shù)字1
/// - num2: 數(shù)字2
/// - Returns: 返回值
func doOperation(num1:Int, num2:Int) -> Int
}
然后就是各個策略的具體實現(xiàn)嘍
class AddOperation: NSObject {
}
extension AddOperation:OperationStrategy{
func doOperation(num1: Int, num2: Int) -> Int {
return num1 + num2
}
}
class SubstractOperation: NSObject {
}
extension SubstractOperation:OperationStrategy{
func doOperation(num1: Int, num2: Int) -> Int {
return num1 - num2
}
}
所有準(zhǔn)備做好就需要執(zhí)行計算的管理類了
class OperationManager: NSObject {
/// 運算
///
/// - Parameters:
/// - num1: 數(shù)字1
/// - num2: 數(shù)字2
/// - strategy: 運算策略
/// - Returns: 運算結(jié)果
public class func operation(num1:Int, num2:Int, strategy:OperationStrategy) -> Int{
return strategy.doOperation(num1: num1, num2: num2)
}
}
使用
print("策略模式")
//根據(jù)需要,選擇相應(yīng)的運算方式就好嘍
let result = OperationManager.operation(num1: 1, num2: 2, strategy: AddOperation())
print("\(result)")
let result = OperationManager.operation(num1: 2, num2: 1, strategy: SubstractOperation())
print("\(result)")
都這里我們就完成了最初的需求,將來需要添加不同的計算方式的時候,添加具體策略的實現(xiàn)就好啦。就不存在改動已有代碼的問題嘍。
上面的幾種設(shè)計模式看完,可能會腦子里有點迷糊,肯能會搞不清楚兩個設(shè)計模式之間的區(qū)別。
下面是我總結(jié)的我比較迷惑的幾個設(shè)計模式之間的區(qū)別:
工廠模式vs策略模式
工廠:注重點是生產(chǎn)不同的對象。
策略:注重點是得到不同的方法。
比如上面的計算器的功能,網(wǎng)上有個解決方案就是使用工廠模式。具體實現(xiàn)就是為每一種運算方法提供一個類。
策略模式vs觀察者模式
策略:注重其他類為自己提供某一功能的具體實現(xiàn)。
觀察者:自身變化對其他類的通知。
這篇文章到這來就結(jié)束啦,小伙伴也許都是一臉懵逼,其實在項目開發(fā)中大家如果謹(jǐn)記面向?qū)ο蟮奶攸c:繼承、封裝、多態(tài)。謹(jǐn)記自己寫出的代碼要高內(nèi)聚、低耦合、易擴(kuò)展。能夠想方設(shè)法、多費點精力使敲出的代碼符合這幾點要求,就在自己無意識的情況下使用了某些設(shè)計模式。
另外就是需要多去閱讀一些知名輪子的源碼,多些代碼,多些經(jīng)過腦子的代碼。
設(shè)計模式還有很多,網(wǎng)上也還有挺多文章的。這里再給大家貼個鏈接,需要的同學(xué)可以看看。
設(shè)計模式