GoF의 디자인 패턴, 감시자 패턴에 대해 알아본다.

핵심 요약

객체 사이에 일 대 다 의존 관계를 정의해두어, 어떤 객체의 상태가 변할 때, 그 객체에 의존성을 가진 다른 객체들이 변화를 통지받고 자동으로 갱신될 수 있게 만든다.

  • 종속자, 게시-구독(Publish-Subscribe)라 불린다.
  • swift index이 이 형태라 생각하면 된다.
  • 03. Notification To Combine 에서 사용하는 Notification도 같은 형태다.

예시

Code

//
//  main.swift
//  Observer
//
//  Created by Choiwansik on 2023/09/26.
//
 
import Foundation
 
internal func main() {
    let diceGame: DiceGame = FairDiceGame()
 
    let player1: Player = EvenBettingPlayer(name: "완식")
    let player2: Player = OddBettingPlayer(name: "최완식")
    let player3: Player = OddBettingPlayer(name: "완숙이")
 
    diceGame.add(player: player1)
    diceGame.add(player: player2)
    diceGame.add(player: player3)
 
    (0..<5).forEach { _ in
        diceGame.play()
        print()
    }
}
 
main()
 
//
//  Player.swift
//  Observer
//
//  Created by Choiwansik on 2023/09/26.
//
 
import Foundation
 
public class Player {
 
    public init(name: String) {
        self.name = name
    }
 
    public func update(diceNumber: Int) {
 
    }
 
    public let name: String
 
}
 
//
//  OddBettingPlayer.swift
//  Observer
//
//  Created by Choiwansik on 2023/09/26.
//
 
import Foundation
 
public class OddBettingPlayer: Player {
 
    override public init(name: String) {
        super.init(name: name)
    }
 
    override public func update(diceNumber: Int) {
        if diceNumber % 2 == 1 {
            print("\(self.name)가 이김!")
        }
    }
 
}
 
//
//  EvenBettingPlayer.swift
//  Observer
//
//  Created by Choiwansik on 2023/09/26.
//
 
import Foundation
 
public class EvenBettingPlayer: Player {
 
    override public init(name: String) {
        super.init(name: name)
    }
 
    override public func update(diceNumber: Int) {
        if diceNumber % 2 == 0 {
            print("\(self.name)가 이김!")
        }
    }
 
}
 
//
//  DiceGame.swift
//  Observer
//
//  Created by Choiwansik on 2023/09/26.
//
 
import Foundation
 
public class DiceGame {
 
    public func add(player: Player) {
        self.players.append(player)
    }
 
    public func play() {
        
    }
 
    public var players = [Player]()
 
}
 
//
//  FairDiceGame.swift
//  Observer
//
//  Created by Choiwansik on 2023/09/26.
//
 
import Foundation
 
public class FairDiceGame: DiceGame {
 
    override public func play() {
        guard let diceNumber = (1...6).randomElement() else {
            return
        }
 
        print("주사위 던짐 \(diceNumber)")
 
        var iter = self.players.makeIterator()
        while let player = iter.next() {
            player.update(diceNumber: diceNumber)
        }
 
    }
    
}
 
주사위 던짐 4
완식가 이김!

주사위 던짐 6
완식가 이김!

주사위 던짐 5
최완식가 이김!
완숙이가 이김!

주사위 던짐 2
완식가 이김!

주사위 던짐 5
최완식가 이김!
완숙이가 이김!

활용성

  • 어떤 추상 개념이 두 가지 양상을 갖고, 하나가 다른 하나에 종속적일 때.
  • 한 객체에 가해진 변경으로 다른 객체를 변경해야 하고, 프로그래머들은 얼마나 많은 객체들이 변경되어야 하는지 몰라도 되는 경우.

참여자

  • Subject(DiceGame)
    • 감시자들을 알고 있는 주체.
    • 감시자들을 붙일 수 있는 인터페이스가 정의되어야 함
  • Observer(Player)
    • 주체에 변화를 관심있어 하는 친구들에게 필요한 인터페이스 정의
  • ConcreteSubject(FairDiceGame)
  • ConcreteObserver(OddBettingPlayer, EvenBettingPlayer)

생각해볼 점

  1. Subject와 Observer 클래스 간에는 추상적인 결합만이 존재한다.
    • 인터페이스로만 얽혀있을 뿐이다.
  2. Broadcast 방식의 교류를 가능하게 한다.
    • 이 패턴에서 주체자는 구체적인 수신자를 알 필요가 없다.
    • 그냥 등록된애한테 다 뿌리고 끝이다.
    • 받는쪽에서 무시할지 받을지를 결정한다.
  3. 예측하지 못한 정보를 갱신할 수 있다. - 감시자들끼리 서로 연결되어 있다고 하자. - 정보 갱신자체의 비용이 큰 상황일 때, 한 정보가 변경되었을 때 연결된 모든 주체들은 하위 감시자들에게 또 수정을 가하게 할 수 있다. - 이 수정은 불필요할 수도 있고, 내가 원하는 정보가 아닐 수도 있다. - 즉, 변경의 이유에 대한 것을 유추하는 것이 어렵다.

Reference