diff --git "a/danadot/1. \353\224\224\354\236\220\354\235\270 \355\214\250\355\204\264\354\235\230 \354\204\270\352\263\204\353\241\234 \353\226\240\353\202\230\352\270\260.md" "b/danadot/1. \353\224\224\354\236\220\354\235\270 \355\214\250\355\204\264\354\235\230 \354\204\270\352\263\204\353\241\234 \353\226\240\353\202\230\352\270\260.md" index 8e59bfd..50a36e5 100644 --- "a/danadot/1. \353\224\224\354\236\220\354\235\270 \355\214\250\355\204\264\354\235\230 \354\204\270\352\263\204\353\241\234 \353\226\240\353\202\230\352\270\260.md" +++ "b/danadot/1. \353\224\224\354\236\220\354\235\270 \355\214\250\355\204\264\354\235\230 \354\204\270\352\263\204\353\241\234 \353\226\240\353\202\230\352\270\260.md" @@ -67,6 +67,7 @@ abstract class Duck( ``` ## 두 클래스를 합치는 방법 * "A에는 B가 있다" + 3️⃣ 상속보다는 **구성(composition)** * 유연성을 향상시킬 수 있다. * 알고리즘군을 별도의 클래스 집합으로 캡슐화 가능 diff --git a/danadot/2. Observer Pattern.md b/danadot/2. Observer Pattern.md new file mode 100644 index 0000000..50e8ff9 --- /dev/null +++ b/danadot/2. Observer Pattern.md @@ -0,0 +1,264 @@ +# 옵저버 패턴 + +## 기상 스테이션용 코드 추가하기 + +```kotlin +class WeatherData { + fun getTemperature(): Float + fun getHumidity(): Float + fun getPressure(): Float + + /** + * 기상 관측 값이 갱신될 때마다 호출 + */ + fun measurementsChanged() { + val temperature = getTemperature() + val humidity = getHumidity() + val pressure = getPressure() + + // 디스플레이 갱신 + currentConditionsDisplay.update(temperature, humidity, pressure) + statisticsDisplay.update(temperature, humidity, pressure) + forecastDisplay.update(temperature, humidity, pressure) + } +} +``` + +## 옵저버 패턴 + +> 💡 옵저버 패턴 +> +> 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고, 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의 + +* 신문사 + 구독자 = 옵저버 패턴 + * 한 객체의 상태가 변경되면 그 객체에 의존하는 모든 객체에게 연락 +* 느슨한 결합 + * 주제는 옵저버가 특정 인터페이스를 구현한다는 사실만 알아요. + * 옵저버는 언제든지 새로 추가할 수 있어요. + * 새로운 형식의 옵저버를 추가할 때에도 주제를 변경할 필요가 전혀 없어요. + * 주제와 옵저버는 서로 독립적으로 재사용할 수 있어요. + * 주제나 옵저버가 달라져도 서로에게 영향을 미치지 않아요. + +🐈‍⬛ 상호작용하는 객체 사이에는 가능하면 느슨한 결합을 사용해야한다. + +## 구현하기 + +```kotlin +fun main() { + val weatherData = WeatherData() + val currentDisplay = CurrentConditionDisplay(weatherData) + + weatherData.changeMeasurements(80f, 65f, 30.4f) +} + +interface Subject { + fun registerObserver(observer: Observer) + fun removeObserver(observer: Observer) + fun notifyObservers() +} + +class WeatherData( + private var observers: ArrayList = arrayListOf() +) : Subject { + + private var temperature: Float = Float.MIN_VALUE + private var humidity: Float = Float.MIN_VALUE + private var pressure: Float = Float.MIN_VALUE + + /** + * subject interface 구현 + */ + override fun registerObserver(observer: Observer) { + observers.add(observer) + } + + override fun removeObserver(observer: Observer) { + observers.remove(observer) + } + + override fun notifyObservers() { + observers.forEach { + it.update(temperature = temperature, humidity = humidity, pressure = pressure) + } + } + + fun measurementsChanged() { + notifyObservers() + } + + fun changeMeasurements(temperature: Float, humidity: Float, pressure: Float) { + this.temperature = temperature + this.humidity = humidity + this.pressure = pressure + measurementsChanged() + } +} + +interface Observer { + fun update(temperature: Float, humidity: Float, pressure: Float) +} + +interface DisplayElement { + fun display() +} + +class CurrentConditionDisplay(weatherData: WeatherData) : Observer, DisplayElement { + + init { + weatherData.registerObserver(this) + } + + private var temperature = Float.MIN_VALUE + private var humidity = Float.MIN_VALUE + + override fun display() { + println("현재 상태: 온도 $temperature F, 습도 $humidity %") + } + + override fun update(temperature: Float, humidity: Float, pressure: Float) { + this.temperature = temperature + this.humidity = humidity + display() + } +} + +class StatisticsDisplay : Observer, DisplayElement { + override fun display() { + TODO("Not yet implemented") + } + + override fun update(temperature: Float, humadity: Float, pressure: Float) { + TODO("Not yet implemented") + } +} + +class ForecastDisplayDisplay : Observer, DisplayElement { + override fun display() { + TODO("Not yet implemented") + } + + override fun update(temperature: Float, humadity: Float, pressure: Float) { + TODO("Not yet implemented") + } +} +``` + +## Pull 방식 + +* 주제가 옵저버로 데이터를 보내는 Push +* 옵저버가 주제로부터 데이터를 당겨오는 Pull + +```kotlin +fun main() { + val weatherData = WeatherData() + CurrentConditionDisplay(weatherData) + ForecastDisplay(weatherData) + + weatherData.changeMeasurements(80f, 65f, 30.4f) +} + +interface Subject { + fun registerObserver(observer: Observer) + fun removeObserver(observer: Observer) + fun notifyObservers() +} + +class WeatherData( + private var observers: ArrayList = arrayListOf() +) : Subject { + + var temperature: Float = Float.MIN_VALUE + private set + + var humidity: Float = Float.MIN_VALUE + private set + + var pressure: Float = Float.MIN_VALUE + private set + + /** + * subject interface 구현 + */ + override fun registerObserver(observer: Observer) { + observers.add(observer) + } + + override fun removeObserver(observer: Observer) { + observers.remove(observer) + } + + override fun notifyObservers() { + observers.forEach { it.update() } + } + + fun changeMeasurements(temperature: Float, humidity: Float, pressure: Float) { + this.temperature = temperature + this.humidity = humidity + this.pressure = pressure + measurementsChanged() + } + + private fun measurementsChanged() { + notifyObservers() + } +} + +interface Observer { + fun update() +} + +interface DisplayElement { + fun display() +} + +class CurrentConditionDisplay( + private val weatherData: WeatherData, +) : Observer, DisplayElement { + + init { + weatherData.registerObserver(this) + } + + private var temperature = Float.MIN_VALUE + private var humidity = Float.MIN_VALUE + + override fun display() { + println("현재 상태: 온도 ${temperature}F, 습도 ${humidity}%") + } + + override fun update() { + // weatherData로부터 데이터 당겨오기 + this.temperature = weatherData.temperature + this.humidity = weatherData.humidity + display() + } +} + +class ForecastDisplay( + private val weatherData: WeatherData +) : Observer, DisplayElement { + init { + weatherData.registerObserver(this) + } + + private var currentPressure = 29.92f + private var lastPressure = Float.MIN_VALUE + + override fun display() { + if (currentPressure > lastPressure) { + println("기상 예보: 날씨가 습해지고 있습니다.") + } else if (lastPressure > currentPressure) { + println("기상 예보: 날씨가 좋아지고 있습니다.") + } else { + println("기상 예보: 또이또이 합니다.") + } + } + + override fun update() { + this.lastPressure = currentPressure + // weatherData로부터 데이터 당겨오기 + this.currentPressure = weatherData.pressure + display() + } +} +``` \ No newline at end of file