Github : https://github.com/junghun9102/DesignPatternTemplate
branch : feature/observer
날씨 정보(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 |