• 옵저버 패턴
    개발공부/객체지향 2024. 8. 4. 23:05
    반응형

    옵저버 패턴은 데이터를 수신하는 주체인 옵저버들이 변한다는 디자인원칙을 기반으로 한다.

     

    다양하게 응용하여 구현할 수 있는 패턴이다.

     

     

    1. UML

     아래는 옵저버 패턴의 UML이다.

     

    예시로 주차장 데이터가 추가되면, 담당자에게 알림을 보내고, 주차장에 New 태그를 붙여주는 프로세스가 필요한 상황이 있다고 해보자. ParkingTopic은 ManagerNotifier, ParkingTagUpdator가 무슨 역할을 하는지, 언제 추가되는지 알필요가 전혀 없다.

     

     

    2. 코드

    • Topic 객체
    interface Topic {
        fun addObserver(observer: Observer)
        fun removeObserver()
        fun notifyObservers()
        // csv 데이터를 전달 받음.
        fun setCsvData(data: String)
    }
    class ParkingTopicListener: Topic {
        private val observers = mutableListOf<Observer>()
        lateinit var latestData: ParkingData
    
        override fun addObserver(observer: Observer) {
            observers.add(observer)
        }
    
        override fun removeObserver() {
            println("remove ${observers.last()::class.simpleName}")
            observers.remove(observers.last())
        }
    
        override fun notifyObservers() {
            observers.forEach { it.update(parking = this.latestData) }
        }
    
        override fun setCsvData(data: String) {
            this.latestData = data.split(",").let {
                ParkingData(
                    parkingId = it[0].toLong(),
                    parkingName = it[1],
                    parkingAddress = it[2],
                )
            }
            this.notifyObservers()
        }
    }
    • Observer 객체
    interface Observer {
        fun update(parking: ParkingData)
    }
    class ParkingTagUpdator: Observer {
        override fun update(parking: ParkingData) {
            this.updateParkingTag(parking.parkingId)
        }
    
        private fun updateParkingTag(parkingId: Long) {
            println("update parking tag: parkingId = $parkingId")
        }
    }
    class ManagerNotifier : Observer {
        override fun update(parking: ParkingData) {
            this.notifyManager(parking.parkingName)
        }
    
        private fun notifyManager(parkingName: String) {
            println("notify manager: parkingId = $parkingName")
        }
    }

     

    // Topic과 Observer사이의 DTO다.
    data class ParkingData(
        val parkingId: Long,
        val parkingName: String,
        val parkingAddress: String,
    )

     

    • 메인 객체
    fun main() {
        val parkingListener = ParkingTopicListener().also {
            it.addObserver(ParkingTagUpdator())
            it.addObserver(ManagerNotifier())
        }
    
        val csvParkingData = "1,테스트 주차장,서울특별시 은평구 노원구"
        parkingListener.setCsvData(csvParkingData)
    
        println()
        parkingListener.removeObserver()
    
        parkingListener.setCsvData(csvParkingData)
    
        println()
        println("최신 데이터: ${parkingListener.latestData}")
    }

     

     

    3. 실행결과

     

     

    4. 옵저버 패턴의 활용

     옵저버 패턴은 다양하게 활용이 가능하다. 위 예제는 Topic 객체가 Observer에게 데이터를 Push 해주는 방식이다. Pull 방식으로도 구현이 가능하다. Pull방식의 장점은 Observer객체가 Topic객체에게 필요한 데이터만 가져올 수 있다는 것이다. 단점은 Observer객체가 Topic의 구현체를 알고 있어야 한다. Topic객체가 넘겨주는 데이터의 갯수와 구현체의 의존성을 적절히 고려하여 구현하는게 필요할 것 같다. 코드의 구현을 넘어서 더 다양한 기능을 사용한 서버 아키텍처로써 Kafka나 Kinesis와 같이 이벤트 드리븐 방식 또는 데이터 스트리밍 방식과 유사하다. Observer가 KafkaListener가 되고 더 세분화된 책임 분리가 가능해진다.

     

    https://tech.socarcorp.kr/dev/2024/07/23/legacy-car-relocation.html

    반응형

    '개발공부 > 객체지향' 카테고리의 다른 글

    커맨드 패턴  (0) 2024.08.18
    데코레이터 패턴  (0) 2024.08.08
    전략 패턴  (0) 2024.07.29

    댓글

Designed by Tistory.