行為模式-觀察者模式(The Observer Pattern)

本文大部分內(nèi)容翻譯至《Pro Design Pattern In Swift》By Adam Freeman,一些地方做了些許修改,并將代碼升級到了Swift2.0,翻譯不當(dāng)之處望多包涵。

觀察者模式(The Observer Pattern)

觀察者設(shè)計(jì)模式定義了對象間的一種一對多的依賴關(guān)系垒酬,以便一個(gè)對象的狀態(tài)發(fā)生變化時(shí)刻蚯,所有依賴于它的對象都得到通知并自動刷新。


示例工程

OS X Command Line Tool工程:

SystemComponents.swift

class ActivityLog {
    func logActivity(activity:String) {
        print("Log: \\(activity)")
    }
}

class FileCache {
    func loadFiles(user:String) {
        print("Load files for \\(user)")
    }
}

class AttackMonitor {
    var monitorSuspiciousActivity: Bool = false {
        didSet {
            print("Monitoring for attack: \\(monitorSuspiciousActivity)")
        }
    }
}

ActivityLog類代表系統(tǒng)的事件日志輸出必尼;FileCache類代表一個(gè)給定的用戶的文件加載;AttackMonitor類代表可疑事件發(fā)生時(shí)的安全服務(wù)篡撵。

Authentication.swift

class AuthenticationManager {
    private let log = ActivityLog()
    private let cache = FileCache()
    private let monitor = AttackMonitor()
    
    func authenticate(user:String, pass:String) -> Bool {
        var result = false
        if (user == "bob" && pass == "secret") {
            result = true
            print("User \\(user) is authenticated")
            // call system components
            log.logActivity("Authenticated \\(user)")
            cache.loadFiles(user)
            monitor.monitorSuspiciousActivity = false
        } else {
            print("Failed authentication attempt")
            // call system components
            log.logActivity("Failed authentication: \\(user)")
            monitor.monitorSuspiciousActivity = true
        }
        return result
    }
}

AuthenticationManager類代表用密碼來認(rèn)證用戶的服務(wù)類判莉。可以看出認(rèn)證成功后會輸出成功日志并加載用戶的文件育谬,失敗后會輸出失敗日志并輸出受攻擊的警告券盅。

main.swift

let authM = AuthenticationManager()
authM.authenticate("bob", pass: "secret")
print("--------------")
authM.authenticate("joe", pass: "shhh")

運(yùn)行程序:

User bob is authenticated
Log: Authenticated bob
Load files for bob
Monitoring for attack: false
--------------
Failed authentication attempt
Log: Failed authentication: joe
Monitoring for attack: true

理解觀察者模式解決的問題

示例中的代碼結(jié)構(gòu)在真正的項(xiàng)目中十分常見,一個(gè)事件的發(fā)生引起了一系列的其它事件的發(fā)生膛檀。



問題發(fā)生在操作初始事件的類里(本例中就是AuthenticationManager類)渗饮,它必須知道觸發(fā)的其它事件的詳細(xì)和它們是如何操作的。如果其中的一個(gè)觸發(fā)類做一些修改宿刮,那么相應(yīng)的初始事件類中也要做相應(yīng)的修改互站。


理解觀察者模式

觀察者模式通過將它們分成被觀察者和觀察者來改變這種關(guān)系。被觀察者持有觀察者的集合僵缺,當(dāng)發(fā)生改變時(shí)就通知它們胡桃。



實(shí)現(xiàn)觀察者模式

實(shí)現(xiàn)觀察者模式的關(guān)鍵是用協(xié)議定義觀察者和被觀察者的協(xié)議。

Observer.swift

protocol Observer : class {
    func notify(user:String, success:Bool)
}

protocol Subject {
    func addObservers(observers:Observer...)
    func removeObserver(observer:Observer)
}

注意到Observer協(xié)議的class關(guān)鍵字磕潮, 這樣做的原因是我們后面要進(jìn)行對象的比較翠胰。

創(chuàng)建被觀察者基類

我們知道被觀察類持有觀察者類的集合,所以同時(shí)需要GCD并發(fā)保護(hù)自脯。

Observer.swift

import Foundation
protocol Observer : class {
    func notify(user:String, success:Bool)
}

protocol Subject {
    func addObservers(observers:Observer...)
    func removeObserver(observer:Observer)
}

class SubjectBase : Subject {
    private var observers = [Observer]()
    private var collectionQueue = dispatch_queue_create("colQ",DISPATCH_QUEUE_CONCURRENT)
    
    func addObservers(observers: Observer...) {
        dispatch_barrier_sync(self.collectionQueue){[weak self] in
            for newOb in observers {
                self!.observers.append(newOb)
            }
        }
    }
    
    
    func removeObserver(observer: Observer) {
        dispatch_barrier_sync(self.collectionQueue){[weak self] in
            self!.observers = self!.observers.filter(){
                $0 !== observer
            }
        }
    }
    
    func sendNotification(user:String, success:Bool) {
        dispatch_sync(self.collectionQueue){ [weak self] in
            for ob in self!.observers {
                ob.notify(user, success: success)
            }
        }
    }
}

實(shí)現(xiàn)被觀察者協(xié)議

Authentication.swift

class AuthenticationManager : SubjectBase {
    func authenticate(user:String, pass:String) -> Bool {
    var result = false
    if (user == "bob" && pass == "secret") {
        result = true
        print("User \\(user) is authenticated")
    } else {
        print("Failed authentication attempt")
    }
    sendNotification(user, success: result)
    return result
    }
}

實(shí)現(xiàn)觀察者協(xié)議

SystemComponents.swift

class ActivityLog : Observer {
    func notify(user: String, success: Bool) {
        print("Auth request for \\(user). Success: \\(success)")
    }
    
    func logActivity(activity:String) {
        print("Log: \\(activity)")
    }
}

class FileCache : Observer {
    func notify(user: String, success: Bool) {
        if (success) {
            loadFiles(user)
        }
    }
        
    func loadFiles(user:String) {
        print("Load files for \\(user)")
    }
}

class AttackMonitor : Observer {
    func notify(user: String, success: Bool) {
            monitorSuspiciousActivity = !success
    }
            
    var monitorSuspiciousActivity: Bool = false {
        didSet {
            print("Monitoring for attack: \\(monitorSuspiciousActivity)")
        }
    }
}

最后我們再看main.swift:

let log = ActivityLog()
let cache = FileCache()
let monitor = AttackMonitor()
let authM = AuthenticationManager()

authM.addObservers(log, cache, monitor)
authM.authenticate("bob", pass: "secret")

print("-----")
authM.authenticate("joe", pass: "shhh")

運(yùn)行程序:

User bob is authenticated
Auth request for bob. Success: true
Load files for bob
Monitoring for attack: false
-----
Failed authentication attempt
Auth request for joe. Success: false
Monitoring for attack: true

我們再添加觀察者就顯得很容易了之景,只要實(shí)現(xiàn)觀察者協(xié)議然后調(diào)用addOberservers方法添加觀察者就行了。


觀察者模式的變形

泛化通知類型

示例中的notify 方法只能接收認(rèn)證的通知膏潮,這其實(shí)是很糟糕的一種設(shè)計(jì)锻狗。

...
func notify(user:String, success:Bool)
...

下面我們做一些修改,使得它所接受的數(shù)據(jù)類型和通知類型都可以多樣化。

Observer.swift

import Foundation

enum NotificationTypes : String {
    case AUTH_SUCCESS = "AUTH_SUCCESS"
    case AUTH_FAIL = "AUTH_FAIL"
}
struct Notification {
    let type:NotificationTypes
    let data:Any?
}

protocol Observer : class {
    func notify(notification:Notification)
}
......

func sendNotification(notification:Notification) {
        dispatch_sync(self.collectionQueue){ [weak self] in
            for ob in self!.observers {
                ob.notify(notification)
            }
        }
    }
.......

接著我們修改SystemComponents.swift :

class ActivityLog : Observer {
    func notify(notification:Notification) {
        print("Auth request for \\(notification.type.rawValue) " + "Success: \\(notification.data!)")
    }
    
    func logActivity(activity:String) {
        print("Log: \\(activity)")
    }
}

class FileCache : Observer {
    func notify(notification:Notification) {
        if (notification.type == NotificationTypes.AUTH_SUCCESS) {
            loadFiles(notification.data! as! String)
        }
    }
        
    func loadFiles(user:String) {
        print("Load files for \\(user)");
    }
}

class AttackMonitor : Observer {
    func notify(notification: Notification) {
        monitorSuspiciousActivity = (notification.type == NotificationTypes.AUTH_FAIL)
    }
        
    var monitorSuspiciousActivity: Bool = false {
        didSet {
            print("Monitoring for attack: \\(monitorSuspiciousActivity)");
        }
    }
}

最后是AuthenticationManager.swift:

class AuthenticationManager : SubjectBase {
    func authenticate(user:String, pass:String) -> Bool {
        var nType = NotificationTypes.AUTH_FAIL
        if (user == "bob" && pass == "secret") {
            nType = NotificationTypes.AUTH_SUCCESS
            print("User \\(user) is authenticated")
        } else {
            print("Failed authentication attempt")
        }
        sendNotification(Notification(type: nType, data: user))
        return nType == NotificationTypes.AUTH_SUCCESS
    }
}

運(yùn)行程序:

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Menlo; color: #ffffff}span.s1 {font-variant-ligatures: no-common-ligatures}

User bob is authenticated
Auth request for AUTH_SUCCESS Success: bob
Load files for bob
Monitoring for attack: false
-----
Failed authentication attempt
Auth request for AUTH_FAIL Success: joe
Monitoring for attack: true
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末轻纪,一起剝皮案震驚了整個(gè)濱河市油额,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌刻帚,老刑警劉巖潦嘶,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異崇众,居然都是意外死亡掂僵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門顷歌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锰蓬,“玉大人,你說我怎么就攤上這事衙吩。” “怎么了溪窒?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵坤塞,是天一觀的道長。 經(jīng)常有香客問我澈蚌,道長摹芙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任宛瞄,我火速辦了婚禮浮禾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘份汗。我一直安慰自己盈电,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布杯活。 她就那樣靜靜地躺著匆帚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪旁钧。 梳的紋絲不亂的頭發(fā)上吸重,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音歪今,去河邊找鬼嚎幸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛寄猩,可吹牛的內(nèi)容都是我干的嫉晶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼车遂!你這毒婦竟也來了封断?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤舶担,失蹤者是張志新(化名)和其女友劉穎坡疼,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衣陶,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡柄瑰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了剪况。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片教沾。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖译断,靈堂內(nèi)的尸體忽然破棺而出授翻,到底是詐尸還是另有隱情,我是刑警寧澤孙咪,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布堪唐,位于F島的核電站,受9級特大地震影響翎蹈,放射性物質(zhì)發(fā)生泄漏淮菠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一荤堪、第九天 我趴在偏房一處隱蔽的房頂上張望合陵。 院中可真熱鬧,春花似錦澄阳、人聲如沸拥知。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽举庶。三九已至,卻和暖如春揩抡,著一層夾襖步出監(jiān)牢的瞬間户侥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工峦嗤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蕊唐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓烁设,卻偏偏與公主長得像替梨,于是被迫代替她去往敵國和親钓试。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容