本文大部分内容翻译至《Pro Design Pattern In Swift》By Adam Freeman,一些地方做了些许修改,并将代码升级到了Swift2.0,翻译不当之处望多包涵。
观察者模式(The Observer Pattern)
观察者设计模式定义了对象间的一种一对多的依赖关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
示例工程
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类代表系统的事件日志输出;FileCache类代表一个给定的用户的文件加载;AttackMonitor类代表可疑事件发生时的安全服务。
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类代表用密码来认证用户的服务类。可以看出认证成功后会输出成功日志并加载用户的文件,失败后会输出失败日志并输出受攻击的警告。
main.swift
let authM = AuthenticationManager()
authM.authenticate("bob", pass: "secret")
print("--------------")
authM.authenticate("joe", pass: "shhh")
运行程序:
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
理解观察者模式解决的问题
示例中的代码结构在真正的项目中十分常见,一个事件的发生引起了一系列的其它事件的发生。
问题发生在操作初始事件的类里(本例中就是AuthenticationManager类),它必须知道触发的其它事件的详细和它们是如何操作的。如果其中的一个触发类做一些修改,那么相应的初始事件类中也要做相应的修改。
理解观察者模式
观察者模式通过将它们分成被观察者和观察者来改变这种关系。被观察者持有观察者的集合,当发生改变时就通知它们。
实现观察者模式
实现观察者模式的关键是用协议定义观察者和被观察者的协议。
Observer.swift
protocol Observer : class {
func notify(user:String, success:Bool)
}
protocol Subject {
func addObservers(observers:Observer...)
func removeObserver(observer:Observer)
}
注意到Observer协议的class关键字, 这样做的原因是我们后面要进行对象的比较。
创建被观察者基类
我们知道被观察类持有观察者类的集合,所以同时需要GCD并发保护。
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)
}
}
}
}
实现被观察者协议
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
}
}
实现观察者协议
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")
运行程序:
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
我们再添加观察者就显得很容易了,只要实现观察者协议然后调用addOberservers方法添加观察者就行了。
观察者模式的变形
泛化通知类型
示例中的notify 方法只能接收认证的通知,这其实是很糟糕的一种设计。
...
func notify(user:String, success:Bool)
...
下面我们做一些修改,使得它所接受的数据类型和通知类型都可以多样化。
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
}
}
运行程序:
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