
使用設(shè)計(jì)模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。 設(shè)計(jì)模式使代碼編寫真正工程化;設(shè)計(jì)模式是軟件工程的基石脈絡(luò),如同大廈的結(jié)構(gòu)一樣。
設(shè)計(jì)原則
| 編寫 | 全稱 | 中文 |
|---|---|---|
| S | Single Responsibility Principle | 單一職責(zé)原則 |
| O | Open Close Principle | 開閉原則 |
| L | Liskov Substitution Principle | 里氏替換原則 |
| I | Interface Segregation Principle | 接口隔離原則 |
| D | Dependence Inversion Principle | 依賴倒置原則 |
| L | Law Of Demeter | 迪米特法則 |
| C | Composite/Aggregate Reuse Principle CARP | 組合/聚合復(fù)用原則 |
前面五種被稱為面向?qū)ο笤O(shè)計(jì)中常用的SOLID原則。
單一職責(zé)原則
理解:不同的類具備不同的職責(zé),各司其職。做系統(tǒng)設(shè)計(jì)是,如果發(fā)現(xiàn)有一個(gè)類擁有了兩種職責(zé),那么就要問一個(gè)問題:可以將這個(gè)類分成兩個(gè)類嗎?如果真的有必要,那就分開,千萬不要讓一個(gè)類干的事情太多。
總結(jié):一個(gè)類只承擔(dān)一個(gè)職責(zé)
例子:我們?cè)O(shè)計(jì)一個(gè)訂單列表,列表分為待支付、待收貨、已收貨等列表,那我們是寫一個(gè)類,使用if判斷是哪個(gè)類型,然后請(qǐng)求相應(yīng)的數(shù)據(jù),還是寫多個(gè)類,分別執(zhí)行各自的功能呢。很多人會(huì)覺的寫一個(gè)類比較省事,但是過多的判斷條件,各種職責(zé)冗余到一個(gè)類中真的好嗎,如果待支付列表需要加一些特殊的功能呢,待收貨也需要加一些功能呢,那這個(gè)類是不是變得條件判斷異常的多。所以還是寫成多個(gè)類,實(shí)現(xiàn)各自的邏輯比較好。其實(shí)另外我們寫列表的Cell,也是一個(gè)道理,分成幾種類型的Cell去寫,而不是一個(gè)Cell實(shí)現(xiàn)幾種類型。
import Foundation
class OrderList: NSObject {//訂單列表
var waitPayList: WaitPayList?//待支付
var waitGoodsList: WaitGoodsList?//待收貨
var receivedGoodsList: ReceivedGoodsList?//已收貨
}
class WaitPayList: NSObject {
}
class WaitGoodsList: NSObject {
}
class ReceivedGoodsList: NSObject {
}
開閉原則
理解:類、模塊、函數(shù),可以去擴(kuò)展,但不要去修改。如果要修改代碼,盡量用繼承或組合的方式來擴(kuò)展類的功能,而不是直接修改類的代碼。當(dāng)然,如果能保證對(duì)整個(gè)架構(gòu)不會(huì)產(chǎn)生任何影響,那就沒必要搞的那么復(fù)雜,直接改這個(gè)類吧。
總結(jié):對(duì)軟件實(shí)體的改動(dòng),最好用擴(kuò)展而非修改的方式。
例子:我們?cè)O(shè)計(jì)支付功能的時(shí)候,會(huì)用到不同的支付方式,我們可以選擇在支付的時(shí)候使用判斷支付條件然后使用不同的支付方式,然而這種設(shè)計(jì)真的好嗎。如果我們添加了一個(gè)支付方法或者刪除了一個(gè)支付方法是不是要改動(dòng)pay方法的邏輯,那每一次的調(diào)整都要改動(dòng)pay方法的邏輯是不是不合理了,依據(jù)開閉原則具體做法應(yīng)該是設(shè)計(jì)擴(kuò)展支付方式來實(shí)現(xiàn)不同的支付。
修改之前代碼
import Foundation
class PayHelper {
func pay(send: PaySendModel) -> Void {
if send.type == 0 {
//支付寶支付
}
else if send.type == 1 {
//微信支付
}
}
}
class PaySendModel {
var type: Int = 0
var info: [String: AnyHashable]?
}
修改之后
import Foundation
class PayHelper {
var processors: [Int: PayProcessor]?
func pay(send: PaySendModel) -> Void {
guard let processors = processors else {return}
guard let payProcessor: PayProcessor = processors[send.type] else {return}
payProcessor.handle(send: send)//支付
}
}
class PaySendModel {
var type: Int = 0
var info: [String: AnyHashable]?
}
protocol PayProcessor {
func handle(send: PaySendModel)
}
class AliPayProcessor: PayProcessor {
func handle(send: PaySendModel) {
}
}
class WeChatPayProcessor: PayProcessor {
func handle(send: PaySendModel) {
}
}
可以看到修改之后的支付,擴(kuò)展起來是不是很方便,增加支付方式只需要繼承PayProcessor就行了,不需要更改pay方法了
里氏替換原則
理解:一個(gè)對(duì)象在其出現(xiàn)的任何地方,都可以用子類實(shí)例做替換,并且不會(huì)導(dǎo)致程序的錯(cuò)誤。換句話說,當(dāng)子類可以在任意地方替換基類且軟件功能不受影響時(shí),這種繼承關(guān)系的建模才是合理的。
總結(jié):子類可以擴(kuò)展父類的方法,但不應(yīng)該復(fù)寫父類的方法。
例子:我們定義汽車的基類,基類里面有行駛的方法,現(xiàn)在我們有個(gè)寶馬車,寶馬車?yán)^承汽車基類,也有行駛方法。現(xiàn)在我們想知道寶馬車的行駛速度是多少,該怎么設(shè)計(jì)呢。
修改之前
import Foundation
class Car {
func run() {
print("汽車跑起來了")
}
}
class BaoMaCar: Car {
override func run() {
super.run()
print("當(dāng)前行駛速度是80Km/h")
}
}
可以看到我們重寫了run方法,增加了汽車行駛速度的邏輯,這樣是不滿足的里氏替換原則的。因?yàn)樗谢怌ar替換成子類BaoMaCar,run方法的行為跟以前不是一模一樣了。
修改之后
import Foundation
class Car {
func run() {
print("汽車跑起來了")
}
}
class BaoMaCar: Car {
func showSpeed() {
print("當(dāng)前行駛速度是80Km/h")
}
}
接口隔離原則
理解:一個(gè)類實(shí)現(xiàn)的接口中,包含了它不需要的方法。將接口拆分成更小和更具體的接口,有助于解耦,從而更容易重構(gòu)、更改。
總結(jié):對(duì)象不應(yīng)被強(qiáng)迫依賴它不使用的方法。
例子:我們定義一個(gè)汽車接口,要求實(shí)現(xiàn)run等方法。
修改之前
import Foundation
protocol ICar {
func run()
func showSpeed()
func playMusic()
}
class Car: ICar {
func run() {
print("汽車跑起來了")
}
func showSpeed() {
print("當(dāng)前行駛速度是80Km/h")
}
func playMusic() {
print("播放音樂")
}
}
可以看到我們定義Car實(shí)現(xiàn)了ICar的接口,但是并不是每個(gè)車都有播放音樂的功能的,這樣對(duì)于一般的低端車沒有這個(gè)功能,對(duì)于他們來說,這個(gè)接口的設(shè)計(jì)就是冗余的。
修改之后
import Foundation
protocol IProfessionalCar {//具備一般功能的車
func run()
func showSpeed()
}
protocol IEntertainingCar {//具備娛樂功能的車
func run()
func showSpeed()
func playMusic()
}
class SangTaNaCar: IProfessionalCar {//桑塔納轎車
func run() {
print("汽車跑起來了")
}
func showSpeed() {
print("當(dāng)前行駛速度是80Km/h")
}
}
class BaoMaCar: IEntertainingCar {//寶馬轎車
func run() {
print("汽車跑起來了")
}
func showSpeed() {
print("當(dāng)前行駛速度是80Km/h")
}
func playMusic() {
print("播放音樂")
}
}
接口隔離原則的要求我們,建立單一接口,不要建立龐大臃腫的接口,盡量細(xì)化接口,接口中的方法盡量少。這通過分散定義多個(gè)接口,可以預(yù)防外來變更的擴(kuò)散,提高系統(tǒng)的靈活性和可維護(hù)性。
<meta charset="utf-8">
<article class="_2rhmJa">
迪米特法則
理解:一個(gè)對(duì)象對(duì)另一個(gè)對(duì)象了解得越多,那么,它們之間的耦合性也就越強(qiáng),當(dāng)修改其中一個(gè)對(duì)象時(shí),對(duì)另一個(gè)對(duì)象造成的影響也就越大。
總結(jié):一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象保持最少的了解,實(shí)現(xiàn)低耦合、高內(nèi)聚。
例子:實(shí)現(xiàn)一個(gè)給汽車加油的設(shè)計(jì),使的我們可以隨時(shí)保證加油的質(zhì)量過關(guān)。
修改之前
import Foundation
class Person {
var car: Car?
func refuel(_ gaso: IGasoline) {
if gaso.isQuality == true {//如果汽油質(zhì)量過關(guān),我們就給汽車加油
car?.refuel(gaso)
}
}
}
class Car {
func refuel(_ gaso: IGasoline) {
print("加\(gaso.name)汽油")
}
}
protocol IGasoline {
var name: String { get }
var isQuality: Bool { get }
}
class Gasoline90: IGasoline {
var name: String = "90號(hào)"
var isQuality: Bool = false
}
class Gasoline93: IGasoline {
var name: String = "93號(hào)"
var isQuality: Bool = true
}
可以看到上面有個(gè)問題,我們?cè)趺粗榔偷馁|(zhì)量是否過關(guān)呢,即時(shí)我們知道,加油判斷油的質(zhì)量這個(gè)事情也不應(yīng)該由我們來做。
修改之后
import Foundation
class Person {//給車加油的人
var car: Car?
func refuel(_ worker: WorkerInPetrolStation, _ gaso: IGasoline) {
guard let car = car else {return}
worker.refuel(car, gaso)
}
}
class WorkerInPetrolStation {//加油站工作人員
func refuel(_ car: Car, _ gaso: IGasoline) {
if gaso.isQuality == true {//如果汽油質(zhì)量過關(guān),我們就給汽車加油
car.refuel(gaso)
}
}
}
class Car {
func refuel(_ gaso: IGasoline) {
print("加\(gaso.name)汽油")
}
}
protocol IGasoline {
var name: String { get }
var isQuality: Bool { get }
}
class Gasoline90: IGasoline {
var name: String = "90號(hào)"
var isQuality: Bool = false
}
class Gasoline93: IGasoline {
var name: String = "93號(hào)"
var isQuality: Bool = true
}
可以看到這樣我們就實(shí)現(xiàn)了低耦合,我們只需要知道有加油站工作人員和要加的汽油就行了,不需要知道太多汽油相關(guān)的知識(shí),以及加油相關(guān)的操作流程,這些都交給了工作人員,這樣是符合我們的迪米特原則的。
組合/聚合復(fù)用原則
理解:合成/聚合復(fù)用原則就是在一個(gè)新的對(duì)象里面使用一些已有的對(duì)象,使之成為新對(duì)象的一部分;新的對(duì)象通過向這些對(duì)象的委派達(dá)到復(fù)用已有功能的目的。它的設(shè)計(jì)原則是:要盡量使用合成/聚合,盡量不要使用繼承。
總結(jié):就是說要少用繼承,多用合成關(guān)系來實(shí)現(xiàn)。
繼承復(fù)用有一定的缺點(diǎn):比如如果基類發(fā)生了改變,那么派生類的的實(shí)現(xiàn)就不得不發(fā)生改變;而且從超類繼承而來的實(shí)現(xiàn)是靜態(tài)的,不可能在運(yùn)行時(shí)發(fā)生改變,因此它的靈活性差并最終會(huì)限制復(fù)用性。
使用組合/聚合復(fù)用原則就解決了繼承復(fù)用的缺點(diǎn)。
例子:我們實(shí)現(xiàn)一個(gè)角色,可以是雇員、經(jīng)理等。

人被繼承到雇員,學(xué)生,經(jīng)理子類。而實(shí)際上,雇員、學(xué)生和經(jīng)理分別描述一種角色,而人可以同時(shí)有幾種不同的角色。比如,一個(gè)人既然是經(jīng)理了就一定是雇員,使用繼承來實(shí)現(xiàn)角色,則只能使用每一個(gè)人具有一種角色,這顯然是不合理的。錯(cuò)誤的原因就是把角色的等級(jí)結(jié)構(gòu)和人的等級(jí)結(jié)構(gòu)混淆起來,把Has-A的關(guān)系誤認(rèn)為是Is-A的關(guān)系,通過下面的改正就可以正確的做到這一點(diǎn)。

從上圖可以看出,每一個(gè)人都可以有一個(gè)以上的角色,所以一個(gè)人可以同時(shí)是雇員又是經(jīng)理。從這個(gè)例子可以看出,當(dāng)一個(gè)類是另一個(gè)類的角色時(shí),不應(yīng)該使用繼承描述這種關(guān)系。
有了以上設(shè)計(jì)原則,我們可以遵循設(shè)計(jì)原則實(shí)現(xiàn)很多好的設(shè)計(jì)模式,下面講講設(shè)計(jì)模式。
創(chuàng)建型模式
在軟件工程中,引自維基百科創(chuàng)建型模式是處理對(duì)象創(chuàng)建的設(shè)計(jì)模式,試圖根據(jù)實(shí)際情況使用合適的方式創(chuàng)建對(duì)象?;镜膶?duì)象創(chuàng)建方式可能會(huì)導(dǎo)致設(shè)計(jì)上的問題,或增加設(shè)計(jì)的復(fù)雜度。創(chuàng)建型模式通過以某種方式控制對(duì)象的創(chuàng)建來解決問題。
創(chuàng)建型模式由兩個(gè)主導(dǎo)思想構(gòu)成。一是將系統(tǒng)使用的具體類封裝起來,二是隱藏這些具體類的實(shí)例創(chuàng)建和結(jié)合的方式。
主要用于創(chuàng)建對(duì)象。
抽象工廠模式
提供一個(gè)接口,用于創(chuàng)建與某些對(duì)象相關(guān)或依賴于某些對(duì)象的類家族,而又不需要指定它們的具體類。通過這種模式可以去除客戶代碼和來自工廠的具體對(duì)象細(xì)節(jié)之間的耦合關(guān)系。
類簇是一種把一個(gè)公共的抽象超類下的一些私有的具體子類組合在一起的架構(gòu)。抽象超類負(fù)責(zé)聲明創(chuàng)建私有子類實(shí)例的方法,會(huì)根據(jù)被調(diào)用方法的不同分配恰當(dāng)?shù)木唧w子類,每個(gè)返回的對(duì)象都可能屬于不同的私有子類。
Cocoa將類簇限制在數(shù)據(jù)存儲(chǔ)可能因環(huán)境而變的對(duì)象生成上。Foundation框架為NSString、NSData、NSDictionary、NSSet、和NSArray對(duì)象定義了類簇。公共超類包括上述的不可變類和與其相互補(bǔ)充的可變類NSMutableString、NSMutableData、NSMutableDictionary、NSMutableSet、和NSMutableArray。

import Foundation
class GzCity {//廣州市有兩個(gè)啤酒廠
var abstractFactory1: IAbstractFactory?
var abstractFactory2: IAbstractFactory?
}
protocol IAbstractFactory {//抽象工廠
func createProductA() -> IProduct
func createProductB() -> IProduct
}
protocol IProduct {
var name: String { get }
}
class BearProduct: IProduct {//啤酒產(chǎn)品
var name: String = "啤酒"
}
class ConcreteFactory1: IAbstractFactory {//啤酒工廠1
func createProductA() -> IProduct {
return BearProduct()
}
func createProductB() -> IProduct {
return BearProduct()
}
}
class ConcreteFactory2: IAbstractFactory {//啤酒工廠2
func createProductA() -> IProduct {
return BearProduct()
}
func createProductB() -> IProduct {
return BearProduct()
}
}
建造者模式
將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。
當(dāng)創(chuàng)建復(fù)雜對(duì)象的算法應(yīng)該獨(dú)立于該對(duì)象的組成部分以及它們的裝配方式時(shí)。
當(dāng)構(gòu)造過程必須允許被構(gòu)造的對(duì)象有不同的表示時(shí)。

import Foundation
protocol IBuilder {
func createProduct() -> IProduct
}
protocol IProduct {
var name: String { get }
}
class BeerProduct: IProduct {
var name: String = "啤酒"
}
class Director {//領(lǐng)導(dǎo)
var builder: ConcreteBuilder?
func construct() {//指導(dǎo)生產(chǎn)
guard let product = builder?.createProduct() else {return}
print("生產(chǎn)產(chǎn)品" + product.name)
}
}
class ConcreteBuilder: IBuilder {//生產(chǎn)者
func createProduct() -> IProduct {
return BeerProduct()
}
}
工廠方法模式
定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類決定實(shí)例化哪一個(gè)類。Factory Method 使一個(gè)類的實(shí)例化延遲到其子類。
當(dāng)一個(gè)類不知道它所必須創(chuàng)建的對(duì)象的類的時(shí)候。
當(dāng)一個(gè)類希望由它的子類來指定它所創(chuàng)建的對(duì)象的時(shí)候。
當(dāng)類將創(chuàng)建對(duì)象的職責(zé)委托給多個(gè)幫助子類中的某一個(gè),并且你希望將哪一個(gè)幫助子類是代理者這一信息局部化的時(shí)候。

import Foundation
class ConcreteCreator: ICreator {//生產(chǎn)者
func factoryMethod() -> IProduct {
return ConcreteProduct()
}
}
protocol ICreator {
func factoryMethod() -> IProduct
}
protocol IProduct {
var name: String { get }
}
class ConcreteProduct: IProduct {
var name: String = "啤酒"
}
原型模式
用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過拷貝這些原型創(chuàng)建新的對(duì)象。
原型模式是非常簡(jiǎn)單的一種設(shè)計(jì)模式, 在多數(shù)情況下可被理解為一種深復(fù)制的行為。在Objective-C中使用原型模式, 首先要遵循NSCoping協(xié)議(OC中一些內(nèi)置類遵循該協(xié)議, 例如NSArray, NSMutableArray等)。剛才我們提到了深復(fù)制, 一圖以蔽之:

下面為UML圖

import Foundation
class Client {
var prototype: IPrototype!
func operation() -> IProduct {
return prototype.clone()
}
}
protocol IPrototype {
func clone() -> IProduct
}
protocol IProduct {
var name: String { get }
}
class ConcreteProduct: IProduct, IPrototype {
var name: String = "啤酒"
func clone() -> IProduct {
let p = ConcreteProduct()
p.name = name
return p
}
}
單例模式
保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問它的全局訪問點(diǎn)。該類需要跟蹤單一的實(shí)例,并確保沒有其它實(shí)例被創(chuàng)建。單例類適合于需要通過單個(gè)對(duì)象訪問全局資源的場(chǎng)合。
有幾個(gè)Cocoa框架類采用單例模式,包括NSFileManager、NSWorkspace、和NSApplication類。這些類在一個(gè)進(jìn)程中只能有一個(gè)實(shí)例。當(dāng)客戶代碼向該類請(qǐng)求一個(gè)實(shí)例時(shí),得到的是一個(gè)共享的實(shí)例,該實(shí)例在首次請(qǐng)求的時(shí)候被創(chuàng)建。

import Foundation
class Singleton {
static let instance: Singleton = Singleton()
init() {
}
}
結(jié)構(gòu)型模式
在軟件工程中結(jié)構(gòu)型模式是設(shè)計(jì)模式,借由一以貫之的方式來了解元件間的關(guān)系,以簡(jiǎn)化設(shè)計(jì)。
主要用于處理類或?qū)ο蟮慕M合。
適配器模式
將一個(gè)類的接口轉(zhuǎn)換成另外一個(gè)客戶希望的接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。

我們可以使用適配器模式,把UITableView的接口進(jìn)行二次封裝,統(tǒng)一對(duì)外回調(diào)我們關(guān)心的接口。比如點(diǎn)擊cell的事件回調(diào)等。
import Foundation
class ListAdaper<T>: UITableViewDelegate, UITableViewDataSource {
var cellClick: ((_ obj: T) -> Void)?
init(_ tableView: UITableView) {
tableView.delegate = self
}
...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
cellClick?(datas[indexPath.row])
}
}
可以看到以上代碼,大大簡(jiǎn)化了接口的復(fù)雜度,適配回調(diào)給我們的接口是我們關(guān)心和使用的到的就行了。節(jié)約了很多的代碼成本,增加了維護(hù)性。
橋接模式
將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。

- Abstraction是一個(gè)虛基類,Operation()調(diào)用Implemetor的OperationImp()方法
- Implemetor父類有兩個(gè)子類A和B。
- 兩個(gè)基類間的關(guān)系是聚合關(guān)系,Abstrction是整體,Implementor是部分,說白了就是Implementor是Abstraction的成員變量。
好了,下面用橋接模式來實(shí)現(xiàn)下面這個(gè)比較簡(jiǎn)單的場(chǎng)景:
花園里有一朵牽?;ê鸵欢淠档せ?,牽?;ㄩ_會(huì)吸引蜜蜂來采蜜,牡丹花開會(huì)吸引蝴蝶來采蜜。
import Foundation
protocol IAbstractInsect {
func bloomImp()
}
protocol IAbstractFlower {
var insect: IAbstractInsect? { get }
func bloom()
}
class QianniuHua: IAbstractFlower {
var insect: IAbstractInsect?
func bloom() {
print("牽牛花開了")
insect?.bloomImp()
}
}
class MudanHua: IAbstractFlower {
var insect: IAbstractInsect?
func bloom() {
print("牡丹花開了")
insect?.bloomImp()
}
}
class Butterfly: IAbstractInsect {
func bloomImp() {
print("蝴蝶來了")
}
}
class Bee: IAbstractInsect {
func bloomImp() {
print("蜜蜂來了")
}
}
let qianniu = QianniuHua.init()
qianniu.insect = Bee.init()
qianniu.bloom()
let mudan = MudanHua.init()
mudan.insect = Butterfly.init()
mudan.bloom()
打印如下:
牽牛花開了
蜜蜂來了
牡丹花開了
蝴蝶來了
組合模式
這種模式將互相關(guān)聯(lián)的對(duì)象合成為樹結(jié)構(gòu),以表現(xiàn)部分-全部的層次結(jié)構(gòu)。它使客戶代碼可以統(tǒng)一地處理單獨(dú)的對(duì)象和多個(gè)對(duì)象的合成結(jié)果。
合成對(duì)象是模型-視圖-控制器聚集模式的一部分。

組合模式最大的優(yōu)點(diǎn)是他的節(jié)點(diǎn)可以自由增加,且調(diào)用節(jié)點(diǎn)方便。
import UIKit
class Composite: NSObject {
var subComposites: NSMutableArray = {NSMutableArray()}()
var parentComposite: Composite?
func addComposite(comp: Composite) {
subComposites.add(comp)
comp.parentComposite = self
}
func removeCompositeAtIndex(index:Int) {
subComposites.remove(at: index)
}
func removeComposite(comp: Composite) {
subComposites.remove(comp)
}
func removeFromParent() {
if (self.parentComposite != nil) {
self.parentComposite?.removeComposite(comp: self)
}
}
}
裝飾模式
這種模式動(dòng)態(tài)地將額外的責(zé)任附加到一個(gè)對(duì)象上。在進(jìn)行功能擴(kuò)展時(shí),裝飾是子類化之外的一種靈活的備選方法。和子類化一樣,采納裝飾模式可以加入新的行為,而又不必修改已有的代碼。裝飾將需要擴(kuò)展的類的對(duì)象進(jìn)行包裝,實(shí)現(xiàn)與該對(duì)象相同的接口,并在將任務(wù)傳遞給被包裝對(duì)象之前或之后加入自己的行為。裝飾模式表達(dá)了這樣的設(shè)計(jì)原則:類應(yīng)該接納擴(kuò)展,但避免修改。
裝飾是用于對(duì)象合成的模式,在您自己的代碼中應(yīng)該鼓勵(lì)使用對(duì)象的合成。然而,Cocoa自己提供了一些基于這種模式的類和機(jī)制。在這些實(shí)現(xiàn)中,擴(kuò)展對(duì)象并不完全復(fù)制它所包裝的對(duì)象的接口,雖然具體實(shí)現(xiàn)中可以使用不同的技術(shù)來進(jìn)行接口共享。
Cocoa在實(shí)現(xiàn)某些類時(shí)用到了裝飾模式,包括NSAttributedString、NSScrollView、和NSTableView。后面兩個(gè)類是復(fù)合視圖的例子,它們將其它一些視圖類的對(duì)象組合在一起,然后協(xié)調(diào)它們之間的交互。
動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就增加功能來說,Decorator模式相比生成子類更為靈活。

通過類別實(shí)現(xiàn)裝飾模式,如果你是一名iOS開發(fā)者,你可能立即會(huì)想到用類別來實(shí)現(xiàn)。沒錯(cuò),用類別實(shí)現(xiàn)可以達(dá)到同樣的效果,而且會(huì)更簡(jiǎn)單。類別是Objective-C的特性,它可以添加類的行為,而不用進(jìn)行子類化,通過類別添加的方法不會(huì)影響類原來的方法,類別也成為類的一部分,并可由其子類繼承。
雖然通過類別可以實(shí)現(xiàn)裝飾模式,但是這并不是一種嚴(yán)格的實(shí)現(xiàn),由類別添加的方法是編譯時(shí)綁定的,而裝飾模式是動(dòng)態(tài)綁定的,另外類別也沒有封裝被擴(kuò)展類的實(shí)例。類別適合裝飾器不多的時(shí)候,上面的例子只有一個(gè)NetData裝飾器,用類別實(shí)現(xiàn)會(huì)更輕量,更容易。
外觀模式
這種模式為子系統(tǒng)中的一組接口提供統(tǒng)一的接口。表觀模式定義一個(gè)更高級(jí)別的接口,通過減少復(fù)雜度和隱藏子系統(tǒng)之間的通訊和依賴性,使子系統(tǒng)更加易于使用。

NSImage類為裝載和使用基于位圖(比如JPEG、PNG、或者TIFF格式)或向量(EPS或PDF格式)的圖像提供統(tǒng)一的接口。NSImage可以為同一個(gè)圖像保持多個(gè)表示,不同的表示對(duì)應(yīng)于不同類型的NSImageRep對(duì)象。NSImage可以自動(dòng)選擇適合于特定數(shù)據(jù)類型和顯示設(shè)備的表示。同時(shí),它隱藏了圖像操作和選擇的細(xì)節(jié),使客戶代碼可以交替使用很多不同的表示。
- 實(shí)現(xiàn)了子系統(tǒng)與客戶端之間的松耦合關(guān)系。
- 客戶端屏蔽了子系統(tǒng)組件,減少了客戶端所需處理的對(duì)象數(shù)目,并使得子系統(tǒng)使用起來更加容易。
在生活中很多地方也用到外觀模式,比如購買基金,我們從基金機(jī)構(gòu)那里購買基金,然后他們幫我們管理我們的基金,去操作和運(yùn)行,我們只管購買和賣出就行了,而不用去管他們內(nèi)部的操作,下面是UML圖和具體實(shí)現(xiàn):

/// 基金類
class Fund {
var gu1 = Stock1()
var gu2 = Stock2()
var gu3 = Stock3()
var nd = NationalDebt()
var rt = Realty()
public func buyFund() {
print("買入基金")
gu1.buy()
gu2.buy()
gu3.buy()
nd.buy()
rt.buy()
}
public func shellFund() {
print("\n賣出基金")
gu1.shell()
gu2.shell()
gu3.shell()
nd.shell()
rt.shell()
}
}
//股票類
class Stock1: Deal {
var dealName: String {
return "股票一"
}
}
class Stock2: Deal {
var dealName: String {
return "股票二"
}
}
class Stock3: Deal {
var dealName: String {
return "股票三"
}
}
class NationalDebt: Deal {
var dealName: String {
return "國(guó)債"
}
}
class Realty: Deal {
var dealName: String {
return "房地產(chǎn)"
}
}
protocol Deal {
var dealName: String {get}
mutating func shell()
mutating func buy()
}
extension Deal {
mutating func shell() {
print("\(dealName)賣出")
}
mutating func buy() {
print("\(dealName)買入")
}
}
let jijin = Fund()
// 基金購買
jijin.buyFund()
// 基金贖回
jijin.shellFund()
打印如下:
買入基金
股票一買入
股票二買入
股票三買入
國(guó)債買入
房地產(chǎn)買入賣出基金
股票一賣出
股票二賣出
股票三賣出
國(guó)債賣出
房地產(chǎn)賣出
以上就是簡(jiǎn)單的外觀模式的實(shí)現(xiàn),定義高層接口,基金的買入和賣出,去使用整個(gè)基金系統(tǒng),而不用去管內(nèi)部是怎么操作的。
享元模式
運(yùn)用共享技術(shù)有效地支持大量細(xì)粒度的對(duì)象。

適用性
一個(gè)應(yīng)用程序使用了大量的對(duì)象。
完全由于使用大量的對(duì)象,造成很大的存儲(chǔ)開銷。
對(duì)象的大多數(shù)狀態(tài)都可變?yōu)橥獠繝顟B(tài)。
如果刪除對(duì)象的外部狀態(tài),那么可以用相對(duì)較少的共享對(duì)象取代很多組對(duì)象。
應(yīng)用程序不依賴于對(duì)象標(biāo)識(shí)。由于Flyweight對(duì)象可以被共享,對(duì)于概念上明顯有別的對(duì)象,標(biāo)識(shí)測(cè)試將返回真值。
例子:我們?cè)谝粋€(gè)界面上生成1000個(gè)花,共有5種花,實(shí)現(xiàn)方式如下。

修改前
- (void)viewDidLoad
{
[super viewDidLoad];
//使用普通模式
for (int i = 0; i < 100000; i++) {
@autoreleasepool {
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
NSInteger minSize = 10;
NSInteger maxSize = 50;
CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
CGRect area = CGRectMake(x, y, size, size);
FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
//新建對(duì)象
UIImageView *imageview = [self flowerViewWithType:flowerType];
imageview.frame = area;
[self.view addSubview:imageview];
}
}
}
- (UIImageView *)flowerViewWithType:(FlowerType)type
{
UIImageView *flowerView = nil;
UIImage *flowerImage;
switch (type)
{
case kAnemone:
flowerImage = [UIImage imageNamed:@"anemone.png"];
break;
case kCosmos:
flowerImage = [UIImage imageNamed:@"cosmos.png"];
break;
case kGerberas:
flowerImage = [UIImage imageNamed:@"gerberas.png"];
break;
case kHollyhock:
flowerImage = [UIImage imageNamed:@"hollyhock.png"];
break;
case kJasmine:
flowerImage = [UIImage imageNamed:@"jasmine.png"];
break;
case kZinnia:
flowerImage = [UIImage imageNamed:@"zinnia.png"];
break;
default:
break;
}
flowerView = [[UIImageView alloc]initWithImage:flowerImage];
return flowerView;
}
上面的代碼可以看到每次要新建一個(gè)flowerView添加到self.view視圖上,這樣做的后果是什么呢,特別是生成大量的對(duì)象的時(shí)候。

可以看到內(nèi)存暫用很大,代碼上看,其實(shí)只有6種花放在不同的位置而已,那我們可以利用享元模式的思想,復(fù)用這6種花,然后繪制到不同位置,而不是增加對(duì)象添加到視圖上。
修改之后
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface FlowerView : UIImageView
{
}
- (void) drawRect:(CGRect)rect;
@end
==================
#import "FlowerView.h"
#import <UIKit/UIKit.h>
@implementation FlowerView
- (void) drawRect:(CGRect)rect
{
[self.image drawInRect:rect];
}
@end
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef enum
{
kAnemone,
kCosmos,
kGerberas,
kHollyhock,
kJasmine,
kZinnia,
kTotalNumberOfFlowerTypes
} FlowerType;
@interface FlowerFactory : NSObject
{
@private
NSMutableDictionary *flowerPool_;
}
- (UIImageView *) flowerViewWithType:(FlowerType)type;
@end
======================
#import "FlowerFactory.h"
#import "FlowerView.h"
@implementation FlowerFactory
- (UIImageView *)flowerViewWithType:(FlowerType)type
{
if (flowerPool_ == nil)
{
flowerPool_ = [[NSMutableDictionary alloc]
initWithCapacity:kTotalNumberOfFlowerTypes];
}
UIImageView *flowerView = [flowerPool_ objectForKey:[NSNumber
numberWithInt:type]];
if (flowerView == nil)
{
UIImage *flowerImage;
switch (type)
{
case kAnemone:
flowerImage = [UIImage imageNamed:@"anemone.png"];
break;
case kCosmos:
flowerImage = [UIImage imageNamed:@"cosmos.png"];
break;
case kGerberas:
flowerImage = [UIImage imageNamed:@"gerberas.png"];
break;
case kHollyhock:
flowerImage = [UIImage imageNamed:@"hollyhock.png"];
break;
case kJasmine:
flowerImage = [UIImage imageNamed:@"jasmine.png"];
break;
case kZinnia:
flowerImage = [UIImage imageNamed:@"zinnia.png"];
break;
default:
break;
}
flowerView = [[FlowerView alloc]
initWithImage:flowerImage];
[flowerPool_ setObject:flowerView
forKey:[NSNumber numberWithInt:type]];
}
return flowerView;
}
@end
#import "ViewController.h"
#import "FlowerFactory.h"
#import "FlyweightView.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// 使用享元模式
FlowerFactory *factory = [[FlowerFactory alloc] init];
NSMutableArray *flowerList = [[NSMutableArray alloc]
initWithCapacity:500];
for (int i = 0; i < 10000; ++i)
{
@autoreleasepool {
FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
//重復(fù)利用對(duì)象
UIImageView *flowerView = [factory flowerViewWithType:flowerType];
CGRect screenBounds = [[UIScreen mainScreen] bounds];
CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
NSInteger minSize = 10;
NSInteger maxSize = 50;
CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
CGRect area = CGRectMake(x, y, size, size);
//新建對(duì)象
NSValue *key = [NSValue valueWithCGRect:area];
//新建對(duì)象
NSDictionary *dic = [NSDictionary dictionaryWithObject:flowerView forKey:key];
[flowerList addObject:dic];
}
}
FlyweightView *view = [[FlyweightView alloc]initWithFrame:self.view.bounds];
view.flowerList = flowerList;
self.view = view;
}
@end
#import <UIKit/UIKit.h>
@interface FlyweightView : UIView
@property (nonatomic, retain) NSArray *flowerList;
@end
==================
#import "FlyweightView.h"
#import "FlowerView.h"
@implementation FlyweightView
extern NSString *FlowerObjectKey, *FlowerLocationKey;
- (void)drawRect:(CGRect)rect
{
for (NSDictionary *dic in self.flowerList)
{
NSValue *key = (NSValue *)[dic allKeys][0];
FlowerView *flowerView = (FlowerView *)[dic allValues][0];
CGRect area = [key CGRectValue];
[flowerView drawRect:area];
}
}
@end

可以看到內(nèi)存已經(jīng)降下來了,我們只是生成了對(duì)象flowerView,但是并沒有add到FlyweightView上,[self.image drawInRect:rect];使用image重新繪制了一個(gè)新的位置去顯示。
代理模式
為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問。

這種模式為某些對(duì)象定義接口,使其充當(dāng)其它對(duì)象的代理或占位對(duì)象,目的是進(jìn)行訪問控制。這種模式可以用于為一個(gè)可能是遠(yuǎn)程的、創(chuàng)建起來開銷很大的、或者需要保證安全的對(duì)象創(chuàng)建代表對(duì)象,并在代表對(duì)象中為其提供訪問控制的場(chǎng)合。它在結(jié)構(gòu)上和裝飾模式類似,但服務(wù)于不同的目的;裝飾對(duì)象的目的是為另一個(gè)對(duì)象添加行為,而代理對(duì)象則是進(jìn)行訪問控制。iOS中大量的使用了代理模式,UITableView,UIScrollView,AppDelegate等。
圖中涉及的角色如下所示:
- 協(xié)議:定義代理和委托的共同接口(方法)
- 委托:根據(jù)指定的協(xié)議,委托代理去完成實(shí)現(xiàn)指定接口(方法)
- 代理:根據(jù)指定的協(xié)議,實(shí)現(xiàn)委托需要實(shí)現(xiàn)的接口(方法)
看實(shí)現(xiàn)代碼
import Foundation
protocol IProxy {//協(xié)議
func charge()
}
class A {//委托
var delegate: IProxy?
func askProxy() {
delegate?.charge()
}
}
class B: IProxy {//代理
func charge() {
print("A委托B充值,B實(shí)現(xiàn)了代理方法charge")
}
}
行為型模式
在軟件工程中, 引自維基百科行為型模式為設(shè)計(jì)模式的一種類型,用來識(shí)別對(duì)象之間的常用交流模式并加以實(shí)現(xiàn)。如此,可在進(jìn)行這些交流活動(dòng)時(shí)增強(qiáng)彈性。
用于描述對(duì)類或?qū)ο笤鯓咏换ズ驮鯓臃峙渎氊?zé)。
職責(zé)鏈模式
使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求,從而避免請(qǐng)求的發(fā)送者和接收者之間的耦合關(guān)系。將這些對(duì)象連成一條鏈,并沿著這條鏈傳遞該請(qǐng)求,直到有一個(gè)對(duì)象處理它為止。

Application Kit框架中包含一個(gè)稱為響應(yīng)者鏈的架構(gòu)。該鏈由一系列響應(yīng)者對(duì)象(就是從NSResponder繼承下來的對(duì)象)組成,事件(比如鼠標(biāo)點(diǎn)擊)或者動(dòng)作消息沿著鏈進(jìn)行傳遞并(通常情況下)最終被處理。如果給定的響應(yīng)者對(duì)象不處理特定的消息,就將消息傳遞給鏈中的下一個(gè)響應(yīng)者。響應(yīng)者在鏈中的順序通常由視圖的層次結(jié)構(gòu)來決定,從層次較低的響應(yīng)者對(duì)象向?qū)哟屋^高的對(duì)象傳遞,頂點(diǎn)是管理視圖層次結(jié)構(gòu)的窗口對(duì)象,窗口對(duì)象的委托對(duì)象,或者全局的應(yīng)用程序?qū)ο蟆J录蛣?dòng)作消息在響應(yīng)者鏈中的確切傳遞路徑是不盡相同的。一個(gè)應(yīng)用程序擁有的響應(yīng)者鏈可能和它擁有的窗口(甚至是局部層次結(jié)構(gòu)中的視圖對(duì)象)一樣多,但每次只能有一個(gè)響應(yīng)者鏈?zhǔn)腔顒?dòng)的—也就是與當(dāng)前活動(dòng)窗口相關(guān)聯(lián)的那個(gè)響應(yīng)鏈。

代碼實(shí)現(xiàn)如下
import Foundation
class DutyHandle : NSObject {
/// 下一個(gè)
var next : DutyHandle?
/// 處理請(qǐng)求操作
func handleRequest(str:String) {
/// 如果可以則直接處理
if (self.canDealWithRequest(str: str)) {
print(str)
}
else {
/// 否則如果有下一個(gè),則下一個(gè)進(jìn)行處理判斷
if ((next) != nil) {
next?.handleRequest(str: str)
}
}
}
/// 判斷能否處理請(qǐng)求
func canDealWithRequest(str:String) -> Bool {
return false
}
}
命令模式
將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而使你可用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化;對(duì)請(qǐng)求排隊(duì)或記錄請(qǐng)求日志,以及支持可取消的操作。請(qǐng)求對(duì)象將一或多個(gè)動(dòng)作綁定在特定的接收者上。命令模式將發(fā)出請(qǐng)求的對(duì)象和接收及執(zhí)行請(qǐng)求的對(duì)象區(qū)分開來。

調(diào)用對(duì)象
NSInvocation類的實(shí)例用于封裝Objective-C消息。一個(gè)調(diào)用對(duì)象中含有一個(gè)目標(biāo)對(duì)象、一個(gè)方法選擇器、以及方法參數(shù)。您可以動(dòng)態(tài)地改變調(diào)用對(duì)象中消息的目標(biāo)及其參數(shù),一旦消息被執(zhí)行,您就可以從該對(duì)象得到返回值。通過一個(gè)調(diào)用對(duì)象可以多次調(diào)用目標(biāo)或參數(shù)不同的消息。
創(chuàng)建NSInvocation對(duì)象需要使用NSMethodSignature對(duì)象,該對(duì)象負(fù)責(zé)封裝與方法參數(shù)和返回值有關(guān)系的信息。NSMethodSignature對(duì)象的創(chuàng)建又需要用到一個(gè)方法選擇器。NSInvocation的實(shí)現(xiàn)還用到Objective-C運(yùn)行環(huán)境的一些函數(shù)。
NSInvocation對(duì)象是分布式、撤消管理、消息傳遞、和定時(shí)器對(duì)象編程接口的一部分。在需要去除消息發(fā)送對(duì)象和接收對(duì)象之間的耦合關(guān)系的類似場(chǎng)合下,您也可以使用。
目標(biāo)-動(dòng)作
目標(biāo)-動(dòng)作機(jī)制使控件對(duì)象—也就是象按鍵或文本輸入框這樣的對(duì)象—可以將消息發(fā)送給另一個(gè)可以對(duì)消息進(jìn)行解釋并將它處理為具體應(yīng)用程序指令的對(duì)象。接收對(duì)象,或者說是目標(biāo),通常是一個(gè)定制的控制器對(duì)象。消息—也被稱為動(dòng)作消息—由一個(gè)選擇器來確定,選擇器是一個(gè)方法的唯一運(yùn)行時(shí)標(biāo)識(shí)。典型情況下,控件擁有的單元對(duì)象會(huì)對(duì)目標(biāo)和動(dòng)作進(jìn)行封裝,以便在用戶點(diǎn)擊或激活控件時(shí)發(fā)送消息(菜單項(xiàng)也封裝了目標(biāo)和動(dòng)作,以便在用戶選擇時(shí)發(fā)送動(dòng)作消息)。目標(biāo)-動(dòng)作機(jī)制之所以能夠基于選擇器(而不是方法簽名),是因?yàn)镃ocoa規(guī)定動(dòng)作方法的簽名和選擇器名稱總是一樣的。
當(dāng)您用Interface Builder構(gòu)建程序的用戶界面時(shí),可以對(duì)控件的動(dòng)作和目標(biāo)進(jìn)行設(shè)置。您因此可以讓控件具有定制的行為,而又不必為控件本身書寫任何的代碼。動(dòng)作選擇器和目標(biāo)連接被歸檔在nib文件中,并在nib文件被解檔時(shí)復(fù)活。您也可以通過向控件或它的單元對(duì)象發(fā)送setTarget:和setAction:消息來動(dòng)態(tài)地改變目標(biāo)和動(dòng)作。
目標(biāo)-動(dòng)作機(jī)制經(jīng)常用于通知定制控制器對(duì)象將數(shù)據(jù)從用戶界面?zhèn)鬟f給模型對(duì)象,或者將模型對(duì)象的數(shù)據(jù)顯示出來。Cocoa綁定技術(shù)則可以避免這種用法,有關(guān)這種技術(shù)的更多信息請(qǐng)參見Cocoa綁定編程主題文檔。
代碼示例
import Foundation
class Command: NSObject {
var receiver: Receiver?
init(receiver:Receiver) {
super.init()
self.receiver = receiver
}
func execute() {
}
func undo() {
}
}
class UpCommand: Command {
override func execute() {
receiver!.soundNumber += 1
}
override func undo() {
receiver!.soundNumber -= 1
}
}
class DownCommand: Command {
override func execute() {
receiver!.soundNumber -= 1
}
override func undo() {
receiver!.soundNumber += 1
}
}
class Receiver: NSObject {
var soundNumber: Int = {0}()
}
class CommandManager: NSObject {
var commandList: NSMutableArray = {NSMutableArray()}()
var command: Command?
func setCommand(command:Command) {
self.command = command
}
func execute() {
if (self.command != nil) {
self.command!.execute()
commandList.add(self.command!)
}
}
func undo() {
if self.commandList.count > 0 {
let command = self.commandList.lastObject as! Command
command.undo()
self.commandList.removeLastObject()
}
}
}
class Client: NSObject {
func begin() {
let receiver = Receiver()
receiver.soundNumber = 15
let upCommand = UpCommand(receiver: receiver)
let manager = CommandManager()
manager.setCommand(command: upCommand)
manager.execute()
}
}
解釋器模式
給定一個(gè)語言,定義它的文法的一種表示,并定義一個(gè)解釋器,這個(gè)解釋器使用該表示來解釋語言中的句子。

代碼示例如下
import Foundation
class Explain: NSObject {
func add(a: Double, b: Double) -> Double {
return a + b
}
func multiply(a: Double, b: Double) -> Double {
return a * b
}
}
迭代器模式
這種模式提供一種順序訪問聚合對(duì)象(也就是一個(gè)集合)中的元素,而又不必暴露潛在表示的方法。迭代器模式將訪問和遍歷集合元素的責(zé)任從集合對(duì)象轉(zhuǎn)移到迭代器對(duì)象。迭代器定義一個(gè)訪問集合元素的接口,并對(duì)當(dāng)前元素進(jìn)行跟蹤。不同的迭代器可以執(zhí)行不同的遍歷策略。

Foundation框架中的NSEnumerator類實(shí)現(xiàn)了迭代器模式。NSEnumerator抽象類的私有具體子類返回的枚舉器對(duì)象可以順序遍歷不同類型的集合—數(shù)組、集合、字典(值和鍵)—并將集合中的對(duì)象返回給客戶代碼。
NSDirectoryEnumerator是一個(gè)不緊密相關(guān)的類,它的實(shí)例可以遞歸地枚舉文件系統(tǒng)中目錄的內(nèi)容。
象NSArray、NSSet、和NSDictionary這樣的集合類都包含相應(yīng)的方法,可以返回與集合的類型相適用的枚舉器。所有的枚舉器的工作方式都一樣。您可以在循環(huán)中向枚舉器發(fā)送nextObject消息,如果該消息返回nil,而不是集合中的下一個(gè)對(duì)象,則退出循環(huán)。
實(shí)現(xiàn)棧的迭代如下
import Foundation
/// 堆棧迭代器
class StackIterator: NSObject {
private lazy var stack = NSMutableArray()
func push(object :Any) {
stack.add(object)
}
func pop() -> Any {
let object = readStackRear
if empty() {
stack.remove(object)
}
return object
}
/// 讀取棧尾
func readStackRear() -> Any {
if empty() {
return NSNull()
}
let object = stack.lastObject
return object!
}
func count() -> Int {
return stack.count
}
func empty() -> Bool {
return stack.count == 0
}
}
實(shí)現(xiàn)隊(duì)列的迭代如下
import Foundation
/// 隊(duì)列迭代器
class QueueIterator: NSObject {
private lazy var quene = NSMutableArray()
func inQuene(object :Any) {
quene.add(object)
}
func outQuene() -> Any {
let object = readQueneHead()
if empty() == false {
quene.remove(object)
}
return object
}
/// 讀取隊(duì)首
func readQueneHead() -> Any {
if empty() {
return NSNull()
}
let object = quene.firstObject
return object!
}
func count() -> Int {
return quene.count
}
func empty() -> Bool {
return quene.count == 0
}
}
實(shí)現(xiàn)迭代器如下
import Foundation
/// 迭代器
class EnumIterator: NSObject {
private(set) lazy var allObjects = NSArray()
private lazy var index = 0
init(allObjects : NSArray) {
super.init()
self.allObjects = allObjects
}
func nextObject() -> Any {
if index >= allObjects.count {
return NSNull()
}
let object = allObjects[index]
index += 1
return object
}
}
中介者模式
用一個(gè)中介對(duì)象來封裝一系列的對(duì)象交互。中介者使各對(duì)象不需要顯式地相互引用,從而使其耦合松散,而且可以獨(dú)立地改變它們之間的交互。
使用中介之前如下圖

使用中介之后如下圖

中介者模式很好的處理了業(yè)務(wù)中組件化方案的強(qiáng)耦合的問題,我們iOS當(dāng)中組件化的實(shí)現(xiàn)都是基于中介者的模式的。其中的Mediator起到至關(guān)重要的作用,Mediator就是我們封裝的組件化的框架。框架方案可參考iOS 組件化方案探索
備忘錄模式
這種模式在不破壞封裝的情況下,捕捉和外部化對(duì)象的內(nèi)部狀態(tài),使對(duì)象在之后可以回復(fù)到該狀態(tài)。備忘錄模式使關(guān)鍵對(duì)象的重要狀態(tài)外部化,同時(shí)保持對(duì)象的內(nèi)聚性。

在iOS常用的實(shí)現(xiàn)備忘錄模式的模塊有歸檔、序列化、CoreData等。
歸檔
歸檔將一個(gè)程序中的對(duì)象以及對(duì)象的屬性(包括屬性和關(guān)系)存儲(chǔ)到檔案上,使之可以保存到文件系統(tǒng)中,或者在不同的處理器和網(wǎng)絡(luò)間傳遞。檔案將程序的對(duì)象圖保存為獨(dú)立于架構(gòu)的字節(jié)流,對(duì)象的標(biāo)識(shí)和對(duì)象之間的關(guān)系都會(huì)被保留。由于對(duì)象的類型和它的數(shù)據(jù)一起被存儲(chǔ),從歸檔的字節(jié)流解碼出來的對(duì)象會(huì)被正常實(shí)例化,實(shí)例化所用的類與原來編碼的類相同。
通常情況下,您希望將程序中需要保存狀態(tài)的對(duì)象歸檔。模型對(duì)象幾乎總是屬于這個(gè)范疇。您通過編碼將對(duì)象寫入到檔案中,而通過解碼將對(duì)象從檔案中讀取出來。通過NSCoder對(duì)象可以執(zhí)行編解碼操作,在編解碼過程中最好使用鍵化的歸檔技術(shù)(需要調(diào)用NSKeyedArchiver和NSKeyedUnarchiver類的方法)。被編解碼的對(duì)象必須遵循NSCoding協(xié)議;該協(xié)議的方法在歸檔過程中會(huì)被調(diào)用。
屬性列表的序列化
屬性列表是一個(gè)簡(jiǎn)單的、具有一定結(jié)構(gòu)的對(duì)象圖序列,它僅使用下面這些類的對(duì)象:NSDictionary、NSArray、NSString、NSData、NSDate、和NSNumber。這些對(duì)象通常也被稱為屬性列表對(duì)象。Cocoa中有幾個(gè)框架類提供了序列化屬性列表對(duì)象,以及定義錄寫對(duì)象內(nèi)容及其層次關(guān)系的特殊數(shù)據(jù)流格式的方法。NSPropertyListSerialization類就提供了將屬性列表對(duì)象序列化為XML或其它優(yōu)化的二進(jìn)制格式的類方法。
如果對(duì)象圖中包含的是簡(jiǎn)單對(duì)象,則在捕捉和外部化對(duì)象及其狀態(tài)時(shí),屬性列表序列化是一種靈活的、可移植的、而又非常適當(dāng)?shù)墓ぞ?。然而,這種形式的序列化有它的限制,它不保留對(duì)象的全部類標(biāo)識(shí),而只保留一些一般的類型(數(shù)組、字典、字符串、等等)。這樣,從屬性列表恢復(fù)出來的對(duì)象可能和原來的類不同,特別是當(dāng)對(duì)象的可變性可能發(fā)生變化時(shí),這就會(huì)帶來問題。屬性列表序列化也不跟蹤在同一對(duì)象中被多次引用的對(duì)象,這可能導(dǎo)致反向序列化時(shí)產(chǎn)生多個(gè)實(shí)例,而在原來的對(duì)象圖中卻只有一個(gè)實(shí)例。
Core Data
Core Data是一個(gè)管理對(duì)象圖,并使其留存的框架和架構(gòu)。正是第二種能力—對(duì)象的留存能力—使Core Data成為備忘錄模式的一種適配形式。
在Core Data架構(gòu)中,中心的對(duì)象稱為被管理對(duì)象上下文,負(fù)責(zé)管理應(yīng)用程序?qū)ο髨D中的模型對(duì)象。在被管理對(duì)象上下文下面是該對(duì)象圖的持久棧,也就是一個(gè)框架對(duì)象的集合,負(fù)責(zé)協(xié)調(diào)模型對(duì)象和外部數(shù)據(jù)存儲(chǔ),比如XML文件或關(guān)系數(shù)據(jù)庫。持久棧對(duì)象負(fù)責(zé)建立存儲(chǔ)中的數(shù)據(jù)和被管理對(duì)象上下文中的對(duì)象之間的映射關(guān)系,在有多個(gè)數(shù)據(jù)存儲(chǔ)的時(shí)候,持久棧對(duì)象將這些存儲(chǔ)表現(xiàn)為被管理對(duì)象上下文中的一個(gè)聚合存儲(chǔ)。
Core Data的設(shè)計(jì)也在很大程度上受到模型-視圖-控制器以及對(duì)象建模模式的影響。
觀察者模式
這種模式定義一種對(duì)象間一對(duì)多的依賴關(guān)系,使得當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生變化時(shí),其它具有依賴關(guān)系的對(duì)象可以自動(dòng)地被通知和更新。觀察者模式本質(zhì)上是個(gè)發(fā)布-定閱模型,主體和觀察者具有寬松的耦合關(guān)系。觀察和被觀察對(duì)象之間可以進(jìn)行通訊,而不需要太多地了解對(duì)方。

在iOS常用的觀察者模式的模塊有通知、KVO等。
通知
Cocoa的通知機(jī)制實(shí)現(xiàn)了一對(duì)多的消息廣播,其實(shí)現(xiàn)方式符合觀察者模式。在這種機(jī)制中,程序里的對(duì)象將自己或其它對(duì)象添加到一或多個(gè)通知的觀察者列表中,每個(gè)通知由一個(gè)全局的字符串(即通知的名稱)標(biāo)識(shí)。希望向其它對(duì)象發(fā)送通知的對(duì)象-也就是被觀察的對(duì)象-負(fù)責(zé)創(chuàng)建一個(gè)通知對(duì)象,并將它發(fā)送到通知中心。通知中心則負(fù)責(zé)確定通知的觀察者,并通過消息將通知發(fā)送給觀察者對(duì)象。通知消息激活的方法必須遵循特定的單參數(shù)簽名格式,方法的參數(shù)就是通知對(duì)象,包含通知的名稱、被觀察的對(duì)象、以及一個(gè)含有補(bǔ)充信息的字典。
通知的發(fā)送是一個(gè)同步的過程。在通知中心將通知廣播給所有的觀察者之前,發(fā)送對(duì)象不會(huì)再得到程序的控制權(quán)。對(duì)于異步的處理方式,控制權(quán)會(huì)在您將通知放入通知隊(duì)列中之后立即返回到發(fā)送對(duì)象,當(dāng)通知到達(dá)隊(duì)列的頂部時(shí),通知中心會(huì)將它廣播出去。
常規(guī)的通知-也就是那些由通知中心廣播的通知-只能在進(jìn)程內(nèi)部傳播。如果您希望將通知廣播給其它進(jìn)程,需要使用分布式通知中心及其相關(guān)的API。
使用通知可以有很多原因。例如,借助通知機(jī)制,您可以根據(jù)程序中其它地方發(fā)生的事件改變用戶界面元素顯示信息的方式?;蛘?,您可以用通知來保證文檔中的對(duì)象在文檔窗口關(guān)閉之前保存自己的狀態(tài)。通知的一般目的是將事件通知給其它程序?qū)ο?,使它們可以做出恰?dāng)?shù)姆磻?yīng)。
但是,通知的接收對(duì)象只能在事件發(fā)生之后進(jìn)行反應(yīng),這和委托機(jī)制有顯著的不同。被委托的對(duì)象有機(jī)會(huì)拒絕或修改委托對(duì)象希望進(jìn)行的操作。另一方面,觀察者對(duì)象不能直接影響一個(gè)即將發(fā)生的操作。
與通知有關(guān)的類有NSNotification(通知對(duì)象)、NSNotificationCenter(用于發(fā)送通知和添加觀察者)、NSNotificationQueue(負(fù)責(zé)管理通知隊(duì)列)、和NSDistributedNotificationCenter。很多Cocoa框架都發(fā)布和發(fā)送通知,其它對(duì)象都可以成為這些通知的觀察者。
KVO
鍵-值觀察是使對(duì)象可以在其它對(duì)象的具體屬性發(fā)生變化時(shí)得到通知的一種機(jī)制。它基于名為NSKeyValueObserving 的非正式協(xié)議。被觀察的屬性可以是簡(jiǎn)單的屬性、一對(duì)一的關(guān)系、或者一對(duì)多的關(guān)系。鍵-值觀察在模型-視圖-控制器模式中特別重要,因?yàn)樗挂晥D對(duì)象-通過控制器層-可以觀察到模型對(duì)象的變化,因此是Cocoa綁定技術(shù)的必要組件(參見"控制器類"部分)。
Cocoa為很多NSKeyValueObserving方法提供了缺省的“自動(dòng)”實(shí)現(xiàn),使所有遵循該協(xié)議的對(duì)象都具有屬性-觀察的能力。
鍵-值觀察和通告機(jī)制類似,但在一些重要的方面也有不同。在鍵-值觀察中,沒有為所有觀察者提供變化通告的中心對(duì)象,而是將變化的通告直接傳遞給觀察對(duì)象。還有,鍵-值觀察直接和具體的對(duì)象屬性相關(guān)聯(lián)。而通告機(jī)制則更廣泛地關(guān)注程序的事件。
參與鍵-值觀察(KVO)的對(duì)象必須滿足特定的要求-或者說是遵循KVO,記憶起來更為方便。對(duì)于自動(dòng)觀察來說,這需要滿足鍵-值編碼的要求(也稱為遵循KVC),并使用遵循KVC的方法(也就是存取方法)。鍵-值編碼是一個(gè)與自動(dòng)獲取和設(shè)置對(duì)象屬性值有關(guān)的機(jī)制(基于相關(guān)的非正式協(xié)議)。
您可以禁用自動(dòng)的觀察者通告,并通過NSKeyValueObserving非正式協(xié)議及相關(guān)范疇中的方法實(shí)現(xiàn)手工通告,從而對(duì)KVO通告進(jìn)行精化。
狀態(tài)模式
允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變時(shí)改變它的行為。對(duì)象看起來似乎修改了它的類。

import Foundation
class Context {
var state: IState?
func request() {
state?.handle()
}
}
protocol IState {
func handle()
}
class ConcreteStateA: IState {
func handle() {
print("狀態(tài)A")
}
}
class ConcreteStateB: IState {
func handle() {
print("狀態(tài)B")
}
}
策略模式
定義一系列的算法,把它們一個(gè)個(gè)封裝起來, 并且使它們可相互替換。本模式使得算法可獨(dú)立于使用它的客戶而變化。

import Foundation
class Context: IContextInterface {
var quickStrategy: IStrategy?
var insertStrategy: IStrategy?
func quickSort() {
quickStrategy?.sort()
}
func insertSort() {
insertStrategy?.sort()
}
}
protocol IContextInterface {
func quickSort()
func insertSort()
}
protocol IStrategy {
func sort()
}
class QuickSortStrategy: IStrategy {
func sort() {
print("快排策略")
}
}
class InsertSortStrategy: IStrategy {
func sort() {
print("插排策略")
}
}
模板方法模式
這種模式為某個(gè)操作中的算法定義框架,并將算法中的某些步驟推遲到子類實(shí)現(xiàn)。模板方法模式使子類可以重定義一個(gè)算法中的特定步驟,而不需要改變算法的結(jié)構(gòu)。

模板方法模式是Cocoa的基本設(shè)計(jì),事實(shí)上也是一般的面向?qū)ο罂蚣艿幕驹O(shè)計(jì)。Cocoa中的模式使一個(gè)程序的定制組件可以將自己掛到算法上,但何時(shí)和如何使用這些定制組件,由框架組件來決定。Cocoa類的編程接口通常包括一些需要被子類重載的方法。在運(yùn)行環(huán)境中,框架會(huì)在自己所執(zhí)行的任務(wù)過程中的某些點(diǎn)調(diào)用這些所謂的一般方法。一般方法為定制代碼提供一個(gè)結(jié)構(gòu),目的是為當(dāng)前正在執(zhí)行且由框架類負(fù)責(zé)協(xié)調(diào)的任務(wù)加入具體程序的的行為和數(shù)據(jù)。
import Foundation
class Client {
var operationC: AbstractClass?
func operation() {
//執(zhí)行
operationC?.templateMethod()
}
}
class AbstractClass {
func templateMethod() {
print("執(zhí)行當(dāng)前邏輯...")
//推遲留給子類處理邏輯...
primitiveOperation1()
primitiveOperation2()
}
func primitiveOperation1() {
assert(false, "此方法需要繼承")
}
func primitiveOperation2() {
assert(false, "此方法需要繼承")
}
}
class ConcreteClass: AbstractClass {
override func primitiveOperation1() {
print("執(zhí)行operation1邏輯")
}
override func primitiveOperation2() {
print("執(zhí)行operation2邏輯")
}
}
訪問者模式
表示一個(gè)作用于某對(duì)象結(jié)構(gòu)中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用于這些元素的新操作。

解析UML如下:
- 定義訪問元素,包括抽象對(duì)象與具體對(duì)象。
- 定義訪問者,在抽象對(duì)象中訪問者定義執(zhí)行動(dòng)作,訪問元素中接收訪問者的方法。訪問元素增加 acceptVisitor(visitor) 方法(接收訪問者),訪問者增加 visitA(A)、visitB(B)、visitC(C) 方法(根據(jù)元素對(duì)象的多少)。
- 通過訪問元素調(diào)用訪問者中的事件。在訪問元素的 acceptVisitor 的實(shí)現(xiàn)方法中調(diào)用 [visitor visitX:self] 執(zhí)行方法。
import Foundation
class Client: NSObject {
func begin() {
let visit1 = VisitorA()
let visit2 = VisitorB()
let element1 = VisitElementA()
let element2 = VisitElementA()
let element3 = VisitElementA()
let element4 = VisitElementB()
let element5 = VisitElementB()
let array = [element1,element2,element3,element4,element5]
for element in array {
let number = arc4random()
if number%2 == 0 {
element.acceptVisit(visit: visit1)
}
else {
element.acceptVisit(visit: visit2)
}
}
}
}
class Visitor: NSObject {
/// 訪問元素A
func visitA(element :VisitElementA) {
}
/// 訪問元素B
func visitB(element :VisitElementB) {
}
}
class VisitorA: Visitor {
override func visitA(element: VisitElementA) {
NSLog("No1 Visit1 %@", element)
/// 用 element 做某些操作
}
override func visitB(element: VisitElementB) {
NSLog("No1 Visit2 %@", element)
/// 用 element 做某些操作
}
}
class VisitorB: Visitor {
override func visitA(element: VisitElementA) {
NSLog("No2 Visit1 %@", element)
/// 用 element 做某些操作
}
override func visitB(element: VisitElementB) {
NSLog("No2 Visit2 %@", element)
/// 用 element 做某些操作
}
}
class VisitElement: NSObject {
func acceptVisit(visit :Visitor) {
}
}
class VisitElementA: VisitElement {
override func acceptVisit(visit :Visitor) {
visit.visitA(element: self)
}
}
class VisitElementB: VisitElement {
override func acceptVisit(visit :Visitor) {
visit.visitB(element: self)
}
}