[iOS]옵저버 패턴(Observer Pattern)

옵저버 패턴 ❓

  • 옵저버 패턴은 한 Object의 상태가 바뀌면 그 객체에 의존(구독) 하는 다른 객체들한테 연락이 가고 자동으로 내용이 갱신되는 방식으로 1:N(1대다) 관계를 정의합니다.
  • 예시로 유튜브에서 구독자에게 알림 메시지를 보내는 것처럼 관찰 중인 객체에서 발생하는 이벤트를 여러 다른 객체에 알리는 메커니즘입니다.
⚠️ 용어 설명
Subject → 구독자(Concrete Observer)가 이벤트를 받을 주요 주제(Subject) 해당 Subject의 특정 이벤트 변경 시 구독하고 있는 구독자들이 해당 이벤트를 수신합니다.
Observer → 구독자들의 부모가 되는 인터페이스 (Java에서는 추상 클래스)
Concrete Observer → Observer 인터페이스를 상속받고, Subject에 등록이 될 객체 && Subject에 등록(구독)이 되면 이벤트를 받게 됩니다.

What is 옵저버 패턴🤷🏻‍♂️❓

  • 관찰 중인 객체에서 발생하는 이벤트를 여러 다른 객체에 알리는 메커니즘을 정의할 수 있는 디자인 패턴입니다.
  • 다른 객체의 상태가 변경될 때마다 어떤 이벤트를 실행하고 싶을 때 주로 사용합니다.
  • 주로 MVC 패턴에서 사용합니다. [ ViewController → Observer(Subscriber) & Model → Subject(Publisher) ]
  • Model은 View Controller의 타입에 대해 알 필요 없이 상태가 변경될 때마다 이를 View Controller에 전달합니다.
  • 여러 개의 View Controller가 하나의 Model의 변경사함을 사용할 수 있게 됩니다.

옵저버 패턴 장&단점 🗡

디자인 패턴들은 서로 상호작용을 하는 객체 사이에서는 가능하면 느슨하게 결합되는 디자인을 사용해야 합니다.

장점 ▾

  1. 변경 사항이 생겨도 무난히 처리할 수 있는 유연한 객체지향 시스템을 구축할 수 있습니다. 이는 객체 사이의 상호의존성을 최소화할 수 있기 때문입니다. (느슨하게 결합되어 있기 때문!! 아래에서 살펴보겠습니다😃)
  2. Open / Close 원칙(개방 폐쇄 원칙)을 지킬 수 있습니다. (개방 폐쇄 원칙: 확장에는 열려있고, 변경에는 닫혀있어야 한다. )

단점 ▾

  1. Observer에게 알림이 가는 순서를 보장할 수 없습니다.
  2. Observer, Subject의 관계가 잘 정의되지 않으면 원하지 않는 동작이 발생할 수 있습니다.

느슨한 결합(Loose Coupling) ❓

  • 느슨한 결합이란 두 객체가 상호작용을 하지만, 서로에 대해 잘 모른다는 점을 의미합니다.
  • 인터페이스를 이용하여 객체간의 느슨한 결합이 가능합니다.
  • 상속을 통한 구현이 아닌 구성(Composition)을 이용해야 합니다.

예시 ▾

Example

  • Dog, Duck, Cat, Mouse Object 와 같이 전혀 관련이 없는 객체들은 Observer 라는 인터페이스로 묶음으로써 주제(Object)는 Observer들의 정보를 구체적으로 알 필요없이 정보 전달을 할 수 있습니다.
  • Dog, Duck, Cat, Mouse Object 와 같은 Observer 객체들은 주제(Object)의 정보를 알 필요 없이 구독을 할 수 있습니다.
  • 위 내용은 객체지향의 다형성을 생각하시면 편합니다 🙂 ▴
  • 옵저버 패턴에서는 주제(Subject || Publisher)와 옵저버(Observer)가 느슨하게 결합되어 있는 객체 디자인을 제공합니다.

그림으로 보기 🌠

1. Duck이 Subject(주제)에 구독을 신청합니다.


 

2. Duck이 구독자 목록(Observer)에 추가 됩니다.


3. Subject에서 특정 이벤트가 발생해서 값이 변경이 됩니다.

    구독자들에게 알림이 갑니다.


4. 구독 해제도 가능합니다.

   Mouse가 구독자 목록에서 탈퇴를 하고 싶어합니다.


5. Mouse 구독이 해제됩니다.


6. 또 다른 이벤트가 발생합니다.

    구독자 목록(Observer)에서 탈퇴한 Mouse를 제외한 모든 구독자에게 이벤트가 전달됩니다.

 


코드로 보기 👨🏻‍💻

  • Subject
// 주체 프로토콜
protocol Subject {
	var observers: [Observer] { get set }
    func subscribe(observer: Observer)
    func unSubscribe(observer: Observer)
    func notify(message: String)
}

// 애플 스토어
class AppleStore: Subject {
	var observers: [Observer]
    
    // 초기화
    init(observers: [Observer]) {
    	self.observers = observers
    }
    
    // 구독
    func subsribe(observer: Observer) {
    	self.observers.append(observer)
    }
    
    // 구독 해제
    func unSubscribe(observer: Observer) {
    	if let idx = self.observers.firstIndex(where: { $0.id == observer.id}) {
        	self.observers.remove(at: idx)
        }
    }
    
    // 알림
    func notify(message: String) {
    	for observer in observers {
        	observer.update(message: message)
        }
    }
}

  • Observer
// 옵저버(Observer) 프로토콜
protocol Observer {
	var id: String { get set }
    func update(message: String)
}

// 고객
class Customer: Observer {
	var id: String
    
    // 초기화
    init(id: String) {
    	self.id = id
    }
    
    // 알림
    func update(message: String) {
    	print("\(id) → \(message) 수신")
    }
}

  • View Controller
func test() {
	let appleStore = AppleStore(observer: [])
    
    let first = Customer(id: "first")
    let second = Customer(id: "second")
    let third = Customer(id: "third")
    
    // 고객 → 애플 스토어 구독
    appleStore.subscribe(observer: first)
    appleStore.subscribe(observer: second)
    appleStore.subscribe(observer: third)
    
    
    // 애플 스토어 알림
    appleStore.notify(message: "첫번째 알림!!")
    
    // third 고객이 구독 해제
    appleStore.unSubscribe(observer: third)
    
    // 애플 스토어 두번째 알림
    appleStore.notify(message: "두번째 알림!!")
    
}

test()

  • 결과화면

결과화면