Github : https://github.com/junghun9102/DesignPatternTemplate

branch : feature/observer

 

 

junghun9102/DesignPatternTemplate

Contribute to junghun9102/DesignPatternTemplate development by creating an account on GitHub.

github.com

 

 

날씨 정보(WeatherData)의 변경에 따라

WeatherBroadcast에서 변경된 날씨를 예보하고

WeatherStatistics에서 데이터를 분석하기 위해 저장하고 누적데이터를 보여주려한다.

 

 


Commit 708033d9

 

WeatherBroadcast

 - update, broadcast

 

WeatherStatistics

 - update, collectData, showCumulativeData

 

WeatherData

 - setMeasurements, mesurementsChanged

 

전체 코드보다 위의 함수에만 집중하면 된다.

 

 

class WeatherBroadcast {

    private lateinit var weatherData: WeatherData

    fun update(weatherData: WeatherData) {
        this.weatherData = weatherData

        broadcast()
    }

    private fun broadcast() {
        StringBuilder()
            .append("Current Weather is ")
            .append("Temperature(").append(weatherData.temperature).append("), ")
            .append("Humidity(").append(weatherData.humidity).append("), ")
            .append("Pressure(").append(weatherData.pressure).append(")")
            .let(::println)
    }
}

 

class WeatherStatistics {

    private val temperatureData: HashMap<Long, Float> = HashMap()
    private val humidityData: HashMap<Long, Float> = HashMap()
    private val pressureData: HashMap<Long, Float> = HashMap()

    fun update(weatherData: WeatherData) {
        collectData(weatherData)
    }

    private fun collectData(weatherData: WeatherData) {
        val currentTime = System.currentTimeMillis()
        temperatureData[currentTime] = weatherData.temperature
        humidityData[currentTime] = weatherData.humidity
        pressureData[currentTime] = weatherData.pressure

        showCumulativeData()
    }

    private fun showCumulativeData() {
        StringBuilder()
            .append("========== Cumulative Weather Data ==========\n")
            .append("temperature : ").append(temperatureData).append('\n')
            .append("humidity : ").append(humidityData).append('\n')
            .append("pressure : ").append(pressureData).append('\n')
            .let(::println)
    }

    private fun getDateStr(time: Long) = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(Date(time))
}

 

 

WeatherBroadcast와 WeatherStatistics는 weatherData 변경에 따라 추가적인 일을 하려한다.

 

update가 되면

WeatherBroadcast는 방송하고(broadcast)

WeatherStatistics는 데이터를 누적하고(collectData)누적데이터를 보여준다(showCumulativeData).

 

 

 

 

class WeatherData(
    private val weatherBroadcast: WeatherBroadcast,
    private val weatherStatistics: WeatherStatistics
) {

    var temperature: Float = 0f
        private set
    var humidity: Float = 0f
        private set
    var pressure: Float = 0f
        private set

    fun setMeasurements(temperature: Float, humidity: Float, pressure: Float) {
        this.temperature = temperature
        this.humidity = humidity
        this.pressure = pressure

        measurementsChanged()
    }

    private fun measurementsChanged() {
        weatherBroadcast.update(this)
        weatherStatistics.update(this)
    }

    override fun toString() = "t $temperature, h $humidity, p $pressure"

}

 

 

WeatherData는 측정된 데이터를 받아 변경하고

WeatherBroadcast와 WeatherStatistics에게 변경된 데이터를 알려준다.

 

 

잘 작동하지만 WeatherData는 누구에게 데이터를 전해줘야할지 알고 있어야한다.

강한 의존성이 생겼다.

 

 

만약 설계상 추가적으로 데이터를 전달할 사용자가 생긴다면?

-> 계속해서 WeatherData에 손을 대야한다.

 

만약 동적으로 데이터를 받고 싶거나 받고 싶지 않은 사용자가 생긴다면?

-> 사용자를 nullable하게 만들어 처리할 수 있으나 사용자가 명확히 정해져야 한다.

 

 

 


Commit d1151ce0

 

interface Observer<T> {
    fun update(data: T)
}

 

WeatherForecast와 WeatherStatistics의 공통적인 코드를 Interface로 정의했다.

 

 

interface Subject<T> {
    val observers: MutableList<Observer<T>>

    fun registerObserver(observer: Observer<T>)
    fun removeObserver(observer: Observer<T>)
    fun notifyObservers()
}

 

WeatherData에서 사용자들을 각각 구현체로 가지고 있던 부분을 observers로 묶고

동적으로 새로운 사용자 등록하고 제거할 수 있는 함수를 만들었다.

또 기존에 사용자들에게 각각 update했던 부분을 notifyObservers로 만들었다.

 

 

 

날씨 정보를 사용하는 사용자를 Observer, 즉 관찰자라고 할 수 있다. WeatherBroadcast, WeatherStatistics

날씨 정보는 Subject, Observable(관찰할 수 있는) 등으로 부른다. WeatherData

 

JavaAPI에서 Observable과 Observer가 있었으나 deprecated 되었다.

 

 

 

 

class WeatherBroadcast(weatherData: Subject<WeatherData>) : Observer<WeatherData> {

    init {
        weatherData.registerObserver(this)
    }

    private lateinit var weatherData: WeatherData

    override fun update(data: WeatherData) {
        this.weatherData = data

        broadcast()
    }

    private fun broadcast() {
        StringBuilder()
            .append("Current Weather is ")
            .append("Temperature(").append(weatherData.temperature).append("), ")
            .append("Humidity(").append(weatherData.humidity).append("), ")
            .append("Pressure(").append(weatherData.pressure).append(")")
            .let(::println)
    }
}

 

class WeatherStatistics(weatherData: Subject<WeatherData>) : Observer<WeatherData> {

    init {
        weatherData.registerObserver(this)
    }

    private val temperatureData: HashMap<Long, Float> = HashMap()
    private val humidityData: HashMap<Long, Float> = HashMap()
    private val pressureData: HashMap<Long, Float> = HashMap()

    override fun update(data: WeatherData) {
        collectData(data)
    }

    private fun collectData(weatherData: WeatherData) {
        val currentTime = System.currentTimeMillis()
        temperatureData[currentTime] = weatherData.temperature
        humidityData[currentTime] = weatherData.humidity
        pressureData[currentTime] = weatherData.pressure

        showCumulativeData()
    }

    private fun showCumulativeData() {
        StringBuilder()
            .append("========== Cumulative Weather Data ==========\n")
            .append("temperature : ").append(temperatureData).append('\n')
            .append("humidity : ").append(humidityData).append('\n')
            .append("pressure : ").append(pressureData).append('\n')
            .let(::println)
    }

}

 

class WeatherData : Subject<WeatherData>{

    override val observers: MutableList<Observer<WeatherData>> = mutableListOf()

    var temperature: Float = 0f
        private set
    var humidity: Float = 0f
        private set
    var pressure: Float = 0f
        private set

    fun setMeasurements(temperature: Float, humidity: Float, pressure: Float) {
        this.temperature = temperature
        this.humidity = humidity
        this.pressure = pressure

        measurementsChanged()
    }

    private fun measurementsChanged() {
        notifyObservers()
    }

    override fun toString() = "t $temperature, h $humidity, p $pressure"

    override fun registerObserver(observer: Observer<WeatherData>) {
        observers.add(observer)
    }

    override fun removeObserver(observer: Observer<WeatherData>) {
        observers.remove(observer)
    }

    override fun notifyObservers() {
        observers.forEach {
            it.update(this)
        }
    }

}

 

 

WeatherForcast와 WeatherStatistics는 생성자에

Subject를 받아 등록하는 것 외에는 동일하게 동작한다.

 

 

WeatherData는 동적으로 사용자를 추가 제거할 수 있는 대가로 코드가 조금 늘었다.

하지만 더 이상 누가 사용하는지 알지 못한다.

특정 구현체를 알지 못하기 때문에 영향을 받지 않는다.

 

 

 


옵저버 패턴

한 객체가 바뀌면 그 객체에 의존하는 다른 객체들한테

연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의한다.

 

 

 

'SoftwareDesign' 카테고리의 다른 글

SRP  (0) 2020.03.01
SOLID 개요  (0) 2020.03.01
2. Strategy  (0) 2020.01.07
1. Singleton  (0) 2019.12.25
DesignPattern 왜 알아야 할까?  (0) 2019.12.23

+ Recent posts