Концептуальный пример
Этот пример показывает структуру паттерна Наблюдатель , а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире Swift.
Example.swift: Пример структуры паттерна
import XCTest
/// Издатель владеет некоторым важным состоянием и оповещает наблюдателей о его
/// изменениях.
class Subject {
/// Для удобства в этой переменной хранится состояние Издателя, необходимое
/// всем подписчикам.
var state: Int = { return Int(arc4random_uniform(10)) }()
/// @var array Список подписчиков. В реальной жизни список подписчиков может
/// храниться в более подробном виде (классифицируется по типу события и
/// т.д.)
private lazy var observers = [Observer]()
/// Методы управления подпиской.
func attach(_ observer: Observer) {
print("Subject: Attached an observer.\n")
observers.append(observer)
}
func detach(_ observer: Observer) {
if let idx = observers.firstIndex(where: { $0 === observer }) {
observers.remove(at: idx)
print("Subject: Detached an observer.\n")
}
}
/// Запуск обновления в каждом подписчике.
func notify() {
print("Subject: Notifying observers...\n")
observers.forEach({ $0.update(subject: self)})
}
/// Обычно логика подписки – только часть того, что делает Издатель.
/// Издатели часто содержат некоторую важную бизнес-логику, которая
/// запускает метод уведомления всякий раз, когда должно произойти что-то
/// важное (или после этого).
func someBusinessLogic() {
print("\nSubject: I'm doing something important.\n")
state = Int(arc4random_uniform(10))
print("Subject: My state has just changed to: \(state)\n")
notify()
}
}
/// Наблюдатель объявляет метод уведомления, который используют издатели для
/// оповещения.
protocol Observer: class {
func update(subject: Subject)
}
/// Конкретные Наблюдатели реагируют на обновления, выпущенные Издателем, к
/// которому они прикреплены.
class ConcreteObserverA: Observer {
func update(subject: Subject) {
if subject.state < 3 {
print("ConcreteObserverA: Reacted to the event.\n")
}
}
}
class ConcreteObserverB: Observer {
func update(subject: Subject) {
if subject.state >= 3 {
print("ConcreteObserverB: Reacted to the event.\n")
}
}
}
/// Давайте посмотрим как всё это будет работать.
class ObserverConceptual: XCTestCase {
func testObserverConceptual() {
let subject = Subject()
let observer1 = ConcreteObserverA()
let observer2 = ConcreteObserverB()
subject.attach(observer1)
subject.attach(observer2)
subject.someBusinessLogic()
subject.someBusinessLogic()
subject.detach(observer2)
subject.someBusinessLogic()
}
}
Output.txt: Результат выполнения
Subject: Attached an observer.
Subject: Attached an observer.
Subject: I'm doing something important.
Subject: My state has just changed to: 4
Subject: Notifying observers...
ConcreteObserverB: Reacted to the event.
Subject: I'm doing something important.
Subject: My state has just changed to: 2
Subject: Notifying observers...
ConcreteObserverA: Reacted to the event.
Subject: Detached an observer.
Subject: I'm doing something important.
Subject: My state has just changed to: 8
Subject: Notifying observers...
Пример из реальной жизни
Example.swift: Пример из реальной жизни
import XCTest
class ObserverRealWorld: XCTestCase {
func test() {
let cartManager = CartManager()
let navigationBar = UINavigationBar()
let cartVC = CartViewController()
cartManager.add(subscriber: navigationBar)
cartManager.add(subscriber: cartVC)
let apple = Food(id: 111, name: "Apple", price: 10, calories: 20)
cartManager.add(product: apple)
let tShirt = Clothes(id: 222, name: "T-shirt", price: 200, size: "L")
cartManager.add(product: tShirt)
cartManager.remove(product: apple)
}
}
protocol CartSubscriber: CustomStringConvertible {
func accept(changed cart: [Product])
}
protocol Product {
var id: Int { get }
var name: String { get }
var price: Double { get }
func isEqual(to product: Product) -> Bool
}
extension Product {
func isEqual(to product: Product) -> Bool {
return id == product.id
}
}
struct Food: Product {
var id: Int
var name: String
var price: Double
/// Food-specific properties
var calories: Int
}
struct Clothes: Product {
var id: Int
var name: String
var price: Double
/// Clothes-specific properties
var size: String
}
class CartManager {
private lazy var cart = [Product]()
private lazy var subscribers = [CartSubscriber]()
func add(subscriber: CartSubscriber) {
print("CartManager: I'am adding a new subscriber: \(subscriber.description)")
subscribers.append(subscriber)
}
func add(product: Product) {
print("\nCartManager: I'am adding a new product: \(product.name)")
cart.append(product)
notifySubscribers()
}
func remove(subscriber filter: (CartSubscriber) -> (Bool)) {
guard let index = subscribers.firstIndex(where: filter) else { return }
subscribers.remove(at: index)
}
func remove(product: Product) {
guard let index = cart.firstIndex(where: { $0.isEqual(to: product) }) else { return }
print("\nCartManager: Product '\(product.name)' is removed from a cart")
cart.remove(at: index)
notifySubscribers()
}
private func notifySubscribers() {
subscribers.forEach({ $0.accept(changed: cart) })
}
}
extension UINavigationBar: CartSubscriber {
func accept(changed cart: [Product]) {
print("UINavigationBar: Updating an appearance of navigation items")
}
open override var description: String { return "UINavigationBar" }
}
class CartViewController: UIViewController, CartSubscriber {
func accept(changed cart: [Product]) {
print("CartViewController: Updating an appearance of a list view with products")
}
open override var description: String { return "CartViewController" }
}
Output.txt: Результат выполнения
CartManager: I'am adding a new subscriber: UINavigationBar
CartManager: I'am adding a new subscriber: CartViewController
CartManager: I'am adding a new product: Apple
UINavigationBar: Updating an appearance of navigation items
CartViewController: Updating an appearance of a list view with products
CartManager: I'am adding a new product: T-shirt
UINavigationBar: Updating an appearance of navigation items
CartViewController: Updating an appearance of a list view with products
CartManager: Product 'Apple' is removed from a cart
UINavigationBar: Updating an appearance of navigation items
CartViewController: Updating an appearance of a list view with products