(2) Observer Pattern

  • by


새 알림이 올 때마다 하단 탭의 배지 번호를 업데이트해야 합니다.

이 시점에서 알림이 올 때마다 FCM 라이브러리의 특정 함수가 호출되면 다음과 같이 구현할 수 있습니다.

class ReceiveData {
    var 채팅탭_뱃지카운트: Int
    var 알림탭_뱃지카운트: Int
    var 전체_뱃지카운트: Int
    ...
}

class FCM {
    let chattingTap = ChattingTap()
    let alarmTap = AlarmTap()
    
    /// 라이브러리에 의해서 새로운 알림이 올 때마다 호출되어지는 함수
    func receiveNewAlert(data: ReceiveData) {
        let (채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트) = data
        chattingTap.update(채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트)
        alarmTap.update(채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트)
    }
    ...
}

class ChattingTap {
    func update(채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트) {
        // 뱃지 수를 업데이트
    }
}
class AlarmTap {
    func update(채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트) {
        // 뱃지 수를 업데이트
    }
}

위의 코드는 FCM 클래스의 receiveNewAlert 함수 내에서 각 Tap 화면 객체를 사용하여 각 객체에 구현된 update 함수를 직접 호출합니다.

여기서 개선할 수 있는 부분은 무엇일까요?

우선 1. FCM 클래스 내에서 각 화면 인스턴스의 함수를 직접 호출하고 있습니다.

이는 배지 수 표현이 필요한 새 화면이 추가될 때마다 새 화면에 대해 FCM을 다음과 같이 변경해야 함을 의미합니다.

class FCM {
    ...
    // 새로운 화면이 추가됨
    let moreTap = MoreTap()
    
    /// 라이브러리에 의해서 새로운 알림이 올 때마다 호출되어지는 함수
    func receiveNewAlert(data: ReceiveData) {
        let (채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트) = data
        chattingTap.update(채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트)
        alarmTap.update(채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트)
        
        // 뱃지 수 표현이 필요한 새로운 화면이 추가
        moreTap.update(채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트)
    }
    ...
    
}

class MoreTap {
    func update(채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트) {
        // 더보기탭 "More" 뱃지 수 = 전체 - (채팅탭 + 알림탭)로 계산...
    }
}

둘째, 현재 코드에서는 모든 update 함수 호출에 필요한 매개 변수가 동일한 상태이므로 2. 동일한 인터페이스 공유할 수 있도록 할 수도 있습니다.

굳이 지금처럼 ReceiveData 값을 해제해 함수 호출시에 넣을 필요는 없는 것 같습니다.

그러나 여전히 남아있는 문제가 있습니다.

moreTap의 경우 배지 수를 계산하려면 ReceiveData의 모든 값이 필요하지만, 3. alarmTap의 경우, 자신에게 불필요한 값을 (채팅 탭 배트 지수, 전체_배지 카운트)를 취하는 문제있습니다.

Observer 패턴은 한 개체의 상태가 변경될 때 여러 구독 개체로 전파되는 방식의 구조 패턴입니다.

이 경우 FCM 객체는 전파 객체(Subject)이고 각 탭 화면은 FCM을 구독하는 객체에 해당합니다.

기존의 개선 전의 코드도 FCM 내부로부터 오브젝트의 update를 직접 호출하는 방법으로 위의 구조를 따를 것으로 보일지도 모릅니다만, Observer 패턴의 핵심은 Subject는 각 Observer가 무엇인지 얼마나 흥미가 없어야 한다 라는 점입니다.

코드로 구현하면 다음과 같습니다.

protocol Observer {
    func update(채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트)
}

class FCM {
    /// 구독자 배열을 선언
    var observerList = (Observer)()
    
    /// 라이브러리에 의해서 새로운 알림이 올 때마다 호출되어지는 함수
    func receiveNewAlert(data: ReceiveData) {
        let (채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트) = data
        observerList.forEach { observer in
            observer.update(채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트)
        }
    }
    /// 외부에서 Observer를 추가할 수 있게 됨
    func addObserver(_ observer: Observer) {
        observerList.append(observer)
    }
    /// 외부에서 Observer를 삭제할 수 있게 됨
    func removeObserver(_ observer: Observer) {
        for i, _observer in observerList.enumerated() {
            if _observer === observer {
                observerList.remove(at: i)
                break
            }
        }
    }
}
class ChattingTap: Observer {
    func update(채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트) {
        // 뱃지 수를 업데이트
    }
}
class AlarmTap: Observer {
    func update(채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트) {
        // 뱃지 수를 업데이트
    }
}
class MoreTap: Observer {
    func update(채팅탭_뱃지카운트, 알림탭_뱃지카운트, 전체_뱃지카운트) {
        // More 뱃지 수 = 전체 - (채팅탭 + 알림탭)
    }
}

1. FCM 클래스 내에서 각 화면 인스턴스의 함수를 직접 호출 하는 부분은, Observer 프로토콜을 구현하는 오브젝트의 배열을 순회하는 부분으로 바뀌어, 가입자가 얼마나 추가/삭제되는지에 관계없이 변하지 않는 영역으로 수정되었습니다.

객체 지향 원칙
원칙 1) 변하는 부분을 찾아서 변하지 않는 부분으로부터 분리한다.


원칙 2) 행동을 구현하는 것이 아니라 인터페이스 (프로토콜)에 “맞추어 사용”한다.


원칙 3) 상속이 아닌 구성(Composition)을 활용한다.

즉, 오브젝트 지향의 원칙의 하나인 「변하는 부분을 찾아, 변하지 않는 부분으로부터 분리」했습니다.

또한 동시에 2. 동일한 인터페이스를 공유하도록 하는 부분도 함께 수정되었습니다.

그래도 alarmTap의 경우 자신에게 불필요한 변수 (채팅 탭 배트 지수)를 얻는 문제가 남아 있습니다.

같은 인터페이스를 공유하면서 발생한 문제이기 때문입니다.

지금까지의 데이터 갱신 방법은, 모두 데이터 갱신이 가입자 방향으로 행해진다 푸시 했습니다.

푸시 과정에서 update 함수의 서명이 update(채팅 탭_배지 카운트, 알림 탭_배지 카운트, 전체_배지 카운트)에서 동일하기 때문에 발생한 문제입니다.

이 경우 가입자에게 자신이 필요한 데이터를 다시 요청합니다.

)합니다.

아래의 그림에서는, notify 부분이 push 에 대응해, get~~BadgeCount() 함수 부분이 pull 에 대응합니다.


코드로 구현하면 거의 다음과 같습니다.

Subject의 FCM은 매개 변수가없는 update 함수 만 호출하고 각 Observer의 update 내에서 필요한 값만 Subject에 요청하고 가져옵니다.

이런 식으로 3. 자신에게 필요없는 값을 (채팅 탭 박쥐 지수, 전체 _ 배지 카운트) 취하는 문제 또한 해결할 수 있습니다.

protocol Subject {
    // 채팅탭 뱃지 수를 가져오는 함수
    func getChattingBadgeCount() -> Int
    // 알림탭 뱃지 수를 가져오는 함수
    func getAlarmBadgeCount() -> Int
    // 더보기탭 뱃지 수를 가져오는 함수
    func getMoreBadgeCount() -> Int
}
class FCM: Subject {
    var observerList = (Observer)()
    var newAlertReceive: Bool = false
    
    private var data = ReceiveData(.zero)
    
    /// 라이브러리에 의해서 새로운 알림이 올 때마다 호출되어지는 함수
    func receiveNewAlert(data: ReceiveData) {
        //
        self.data = data
        
        observerList.forEach { observer in
            observer.update()
        }
    }
    func addObserver(_ observer: Observer) {
        observerList.append(observer)
    }
    func removeObserver(_ observer: Observer) {
        for i, _observer in observerList.enumerated() {
            if _observer === observer {
                observerList.remove(at: i)
                break
            }
        }
    }
    func getChattingBadgeCount() -> Int {
        return data.채팅탭_뱃지카운트
    }
    func getAlarmBadgeCount() -> Int {
        return data.알림탭_뱃지카운트
    }
    func getMoreBadgeCount() -> Int {
        return data.전체_뱃지카운트 - (data.채팅탭_뱃지카운트 + data.알림탭_뱃지카운트)
    }
}

class AlarmTap: Observer {
    ...
    func update(subject: Subject) {
        let badgeCount = subject.getAlarmBadgeCount()
        // 이후 획득한 뱃지 수로 업데이트
        ...
    }
}
class ChattingTap: Observer {
    ...
    func update(subject: Subject) {
        let badgeCount = subject.getChattingBadgeCount()
        // 이후 획득한 뱃지 수로 업데이트
        ...
    }
}
class MoreTap: Observer {
    ...
    func update(subject: Subject) {
        let badgeCount = subject.getMoreBadgeCount()
        // 이후 획득한 뱃지 수로 업데이트
        ...
    }
}

참고로 Foundation 프레임워크의 NotificationCenter 클래스는 위의 Observer 패턴의 추상화된 구현에 해당합니다.

위의 코드의 Subject가 ObserverList를 관리하고 직접 통지하면 NotificationCenter는 통지(전송)하는 부분도 제어할 수 있습니다.

정리

Observer Pattern은 오브젝트의 상태 변화를 관찰하는 Observer, 즉 Observer의 리스트를 오브젝트에 등록하여, 상태가 변경될 때마다 오브젝트가 리스트의 각 Observer에 직접 통지하도록 합니다.