티스토리 뷰

옵저버 패턴

옵저버 패턴(Observer Pattern)이란, GoF가 소개한 디자인 패턴 중 하나로 관찰 대상이 되는 객체가 변경되면 대상 객체를 관찰하고 있는 옵저버(observer)에게 변경사항을 통지(notify)하는 디자인 패턴을 말한다. 옵저버 패턴을 사용하면 객체 간의 상호작용을 수월하게 하고 효과적으로 데이터를 전달할 수 있다.


옵저버 패턴 구조

  • 옵저버 패턴은 관찰 대상인 서브젝트(Subject)와 이를 관찰하는 옵저버(Observer)로 이루어져 있다.
  • 하나의 서브젝트에는 1개 또는 여러 개의 옵저버를 등록할 수 있다.
  • 서브젝트의 상태가 변경되면 자신을 관찰하는 옵저버들에게 변경사항을 통지한다.
  • 서브젝트로 변경사항을 통지 받은 옵저버는 부가적인 처리를 한다.

  • 옵저버 패턴은 서브젝트와 옵저버를 상속하는 구체화(Concreate) 클래스가 존재한다.
  • 구체화 클래스는 서브젝트와 옵저버에 대한 상세 구현을 작성한다.

Subject Function

  • add : 서브젝트의 상태를 관찰할 옵저버를 등록한다.
  • remove : 등록된 옵저버를 삭제한다.
  • notify : 서브젝트 상태가 변경되면 등록된 옵저버에 통지한다.

Observer Function

  • update : 서브젝트의 notify 내부에서 호출되며, 서브젝트의 변경에 따른 부가 기능을 처리한다.

옵저버 패턴 예제 코드

class Coffee(val name: String)

// Subject
class Barista : Observable() {

    private lateinit var coffeeName: String

    fun orderCoffee(name: String) {
        this.coffeeName = name
    }

    fun makeCoffee() {
        setChanged()
        notifyObservers(Coffee(this.coffeeName))
    }
}

// Observer
class Customer(val name: String) : Observer {

    override fun update(o: Observable?, arg: Any?) {
        val coffee = arg as Coffee
        println("${name}이 ${coffee.name}를 받았습니다.")
    }

}

fun main() {
    val barista = Barista()
    barista.orderCoffee("아이스 아메리카노")

    val customer1 = Customer("고객1")
    val customer2 = Customer("고객2")
    val customer3 = Customer("고객3")

    barista.addObserver(customer1)
    barista.addObserver(customer2)
    barista.addObserver(customer3)

    barista.makeCoffee()
}

  • Customer 클래스는 Observer 인터페이스를 구현하여 Barista 클래스가 커피를 완성하면 통지를 받아서 update 함수에서 처리한다.
  • Barista 클래스는 Observable 클래스를 상속하여 고객이 주문한 커피가 만들어지면 notifyObserver로 고객에게 만들어진 Coffee 객체를 전달한다. 이때 setChanged를 먼저 호출하여 변경 여부를 내부에 저장한다.
  • Customer 클래스가 Barista 클래스를 관찰하기 위해 addObserver로 등록한다.

옵저버 패턴의 장점

  • 옵저버 패턴을 사용하지 않았다면 고객은 일정 간격으로 커피가 완성됐는지 바리스타에게 확인하는 처리가 있어야 한다.
  • 간격이 너무 짧으면 변경된 상태를 빠르게 확인할 수 있지만 매번 불필요한 호출이 발생하므로 성능상 문제가 발생할 수 있고, 간격이 너무 길면 변경된 상태를 즉시 확인할 수 없으므로 실시간성이 떨어지게 된다.
  • 옵저버 패턴은 관찰자인 옵저버가 서브젝트의 변화에 신경쓰지 않고, 상태 변경의 주체인 서브젝트가 변경사항을 옵저버에게 알려줌으로써 앞서 언급한 문제를 해결할 수 있다.
  • 옵저버 패턴은 데이터를 제공하는 측에서 데이터를 소비하는 측에 통지하는 Push-Based이다.


이터레이터 패턴

이터레이터 패턴(Iterator Pattern)은 데이터의 집합에서 데이터를 순차적으로 꺼내기 위해 만들어진 디자인 패턴을 말한다. 이터레이터 패턴을 사용하면 컬렉션이 변경되더라도 동일한 인터페이스를 사용해 데이터를 꺼내올 수 있기 때문에 변경사항 없이 사용할 수 있다. 데이터의 집합이 얼만큼의 크기를 가졌는지 알 수 없는 경우, 이터레이터 패턴을 사용하면 순차적으로 데이터를 꺼내올 수 있다.


  • 애그리게잇(Aggregate)은 요소들의 집합체를 나타낸다.
  • 이터레이터는 집합체 내부에 구현된 iterator를 이용해 생성한다.
  • 이터레이터를 사용하는 클라이언트는 hasNext 함수를 사용해 데이터가 존재하는지 검사하고, next 함수를 사용해 데이터를 꺼낸다.

이터레이터 패턴 예제 코드

data class Car(val brand: String)

class CarIterable(
    val cars: List<Car> = listOf()
) : Iterable<Car> {

    override fun iterator(): Iterator<Car> = CarIterator(cars)

}

class CarIterator(
    val cars: List<Car> = listOf(),
    var index: Int = 0
) : Iterator<Car> {

    override fun hasNext(): Boolean = cars.size > index

    override fun next(): Car = cars[index++]
}

fun main() {
    val carIterable = CarIterable(listOf(Car("페라리"), Car("포르쉐"), Car("BMW")))

    val iterator = carIterable.iterator()
    while (iterator.hasNext()) {
        println("Brand : ${iterator.next()}")
    }
}

  • CarIterator 클래스는 Iterable 인터페이스를 구현하여 CarsIterator를 생성하는 Iterator 함수를 오버라이드한다.
  • CarIterator 클래스는 Iterator 인터페이스를 구현하여 데이터가 존재하는지 확인하는 hasNext, 데이터가 존재하면 가져오는 next 함수를 오버라이드한다.
  • while문 내부에선 hasNext를 사용하여 데이터를 모두 가져올때까지 반복하고 데이터를 출력한다.

옵저버 패턴과 차이점

  • 데이터를 제공한다는 관점에서 이터레이터 패턴과 옵저버 패턴은 유사하다.
  • 이터레이터 패턴은 애그리게잇(Aggregate)이 내부에 데이터를 저장하고 이터레이터를 사용해 데이터를 순차적으로 당겨오는 방식이기 때문에 Pull-Based이다.
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함