본문 바로가기

Kotlin

[Kotlin] 익명객체와 옵저버 패턴

이번 시간에는 안드로이드에서 리스너에 해당하는 옵저버 패턴에 대해 알아보고자 한다. 필자는 리스너를 정말 많이 사용했지만 이것이 옵저버 패턴인지 모르고 사용해왔었다. 그래서 이번 포스팅을 통해 자세히 알아보고자 한다.

 

 

옵저버 : 이벤트가 일어나는 것을 감시하는 감시자 역할

안드로이드에서는 키의 입력, 터치의 발생, 데이터의 수신등 함수로 직접 요청하지 않았지만 시스템 또는 루틴에 의해 발생하게 되는 동작들을 이벤트라고 부른다.

이 이벤트가 발생할 때마다 즉각적으로 처리할 수 있도록 만드는 프로그래밍 패턴을 옵저버 패턴이라고 부른다.

 

옵저버 패턴을 구현할 때는 두 개의 클래스가 필요하다.

-> 이벤트를 수신할 클래스 A / 이벤트의 발생 및 전달을 할 클래스 B

 

이 두개의 클래스는 어떻게 통신을 할까?

-> 인터페이스를 끼워넣는다.

 

B  : 자신의 이벤트를 받을 수 있는 인터페이스를 만들어 공개한다.

A  : 이를 구현하여 B에 넘겨줌

이때 이 인터페이스를 ‘observer’ 또는 코틀린에서는 ‘listener’라고 부르며 

이렇게 이벤트를 넘겨주는 행위를 ‘’callback’이라고 함

 

 

옵저버 패턴을 구현해보자

 

fun main() {
    EventPrinter().start()
}

// 이 인터페이스는 이벤트가 발생할 때 숫자를 반환해줄 예정
interface EventListener {
    // onEvent추상함수를 만들고 패러미터로 정수값을 넘겨줄 수 있도록 함
    fun onEvent(count : Int)
    // 리스너를 통해 이벤트를 반환하는 함수 이름은 관례적으로 'on(행위)'라는 규칙을 따름
}

class Counter(var listener: EventListener) {
    fun count() {
        for (i in 1..100) {
            if (i%5 == 0) listener.onEvent(i)
        }
    }
}



class EventPrinter: EventListener {
    override fun onEvent(count: Int) {
        println("${count}-")
    }


    fun start() {
        val counter = Counter(this)
        counter.count()
        // Counter의 인스턴스를 만들되 this라는 키워드로 EventListener 구현부를 넘겨줌
        // 그리고 count를 시작하게 함
        // 여기서 this는 EventPrinter 객체 자신을 나타내지만 (this는 키워드가 사용된 객체자신을 참조하는 키워드)
        // 받는 쪽에서 'EventListener만' 요구했기때문에 EventListener 구현부만 넘겨주게 됨
        // 이를 객체지향의 다형성이라 함
    }

}

 

상속받아 만들어진 클래스는 수퍼클래스의 기능들을 포함하여 제작되었으므로 수퍼클래스에서 정의한 부분만 따로 넘겨줄 수 있다.

그리고 main에서는 단지 EventPrinter의 인스턴스를 생성하고 start함수만 호출해주면 된다.

 

그런데 EventPrinter가 EventListener를 상속받아 구현하지 않고 

임시로 만든 별도의 EventListener 객체를 대신 넘겨줄 수도 있다.

 

이것을 이름이 없는 객체라 하여 익명객체라고 한다.

 

익명객체 : 익명 클래스의 인스턴스

익명 클래스에 대해 잠깐 알아보자.

 

• class 키워드를 이용하여 명시적으로 선언한 클래스가 아니다.

• 클래스를 한 번만 사용하면 될 때 유용하다.

     -> 객체를 하나만 만들고, 바로 쓸 경우.

• object 키워드를 사용한다.

• 기존의 클래스를 상속하여 사용하여야 한다. 상속을 명시하지 않은 경우 Any를 상속한다.

     - 익명 클래스는 타입이 없기 때문에, 익명 객체의 타입은 익명 클래스가 상속한 부모 클래스이다.

     * 클래스 타입은 클래스명으로 만들어지기 때문에 익명 클래스는 타입이 있을 수 없는 듯하다.

• 익명 클래스에서 새로 추가한 멤버는 외부에서 접근할 수 없다.

     - 상속받은 멤버를 오버라이드한 것만 외부에서 접근이 가능하다.

     - 새로 추가한 멤버는 클래스 내부에서만 사용 가능하다.

 

 

fun main() {
    EventPrinter().start()
}

interface EventListener {
    fun onEvent(count: Int)
}

class Counter(var listener: EventListener) {
    fun count() {
        for (i in 1..100) {
            if (i % 5 == 0) listener.onEvent(i)
        }
    }
}

// 전과 다르게 상속받지 않고 바로 start함수 만듦
class EventPrinter {
    fun start() {
        // counter 변수를 만들 때 패러미터에 익명객체를 만들어 넘김 -> 여기에 object 키워드 사용됨
        // var counter = Counter(익명객체?)
        // 이 긴 문장이 익명함수로 넘길 패러미터 단 한 개
        var counter = Counter(object : EventListener {
            // object를 쓰고 EventListener를 상속받도록 하고 onEvent 오버라이드 
            override fun onEvent(count: Int) {
                print("${count}-")
            }
        })
        counter.count() // 같은 결과 나옴
    }
}

 

object와 형태가 유사하지만 차이는 이름이 없다는 점이다.

이렇게 만들면 인터페이스를 구현한 객체를 코드 중간에도 즉시 생성하여 사용가능하다.

 

Observer 패턴은 이벤트를 기반으로 동작하는 모든 코드에서 광범위하게 쓰이는 방식이므로 그 구조를 이해하는 것이 중요하다.