Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions danadot/1. 디자인 패턴의 세계로 떠나기.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ abstract class Duck(
```
## 두 클래스를 합치는 방법
* "A에는 B가 있다"

3️⃣ 상속보다는 **구성(composition)**
* 유연성을 향상시킬 수 있다.
* 알고리즘군을 별도의 클래스 집합으로 캡슐화 가능
Expand Down
264 changes: 264 additions & 0 deletions danadot/2. Observer Pattern.md
Original file line number Diff line number Diff line change
@@ -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<Observer> = 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<Observer> = 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()
}
}
```