어떤 이벤트에 대해, 그 이벤트가 최초로 발생했을 때와 그 이후에 발생했을 때를 다르게 처리해야하는 경우가 있다.
보통은 이럴 때 isFirst
같은 변수를 하나 만들어두고 이벤트 핸들러에서 분기처리하는 식으로 구현하는데, 이벤트에 대한 처리를 객체로 캡슐화하면 조건문을 제거할 수 있다.
변수를 사용하는 방식
private var isFirst = true
private fun handleEvent() {
if (isFirst) {
isFirst = false
// 최초 발생 시 처리할 것들
} else {
// 두 번째부터 처리할 것들
}
}
// 이벤트가 발생하면 호출되는 메서드
fun onEvent() {
handleEvent()
}
객체로 캡슐화한 방식
아래는 메서드에 대한 참조 변수를 사용하여 구현한 것이다.
private fun handleEventFirstTime() {
eventHandler = ::handleEvent // 이후부터는 handleEvent를 호출하도록
// 최초 발생 시 처리할 것들
}
private fun handleEvent() {
// 두 번째부터 처리할 것들
}
private var eventHandler = ::handleEventFirstTime // 최초의 이벤트를 처리하는 핸들러로 초기화
// 이벤트가 발생하면 호출되는 메서드
fun onEvent() {
eventHandler()
}
이렇게 하면 최초에 할당되어있던 handleEventFirstTime
이 자신을 호출한 eventHandler
의 참조를 변경하게 되어, 이후부터는 eventHandler
를 invoke 하면 handleEvent
가 호출된다.
메서드 참조를 사용한 방식이 아니라 객체로 캡슐화한 방식이라고 소개한 이유는, 우선 Java와 Kotlin에서는 메서드 역시 내부적으로는 객체로 구현되며, 다른 객체지향 언어로 구현하고자 하더라도 동일한 기능을 하는 클래스를 선언해서 구현할 수 있기 때문이다. 객체지향 언어가 아닌 언어, 예를 들어 C언어로도 함수포인터를 사용하면 구현할 수 있다. 객체지향은 패러다임이지 언어나 문법을 말하는 것이 아니다. Java로도 절차지향적 프로그래밍을 할 수 있고 C언어로도 객체지향적 프로그래밍을 할 수 있다.
사실 이것은 상태(State) 패턴을 구현한 것이다
사실 위와 같은 구현은 상태가 두 개인 상태 패턴을 구현한 것이다. 디자인패턴을 설명하는 자료들을 보면 각 상태를 나타내는 인터페이스를 선언하고 이를 구현하는 클래스들을 작성하는 식으로 되어있지만, 이는 패턴의 핵심 아이디어를 명확하게 전달하기 위함일 뿐 반드시 거기에 나와있는 클래스 다이어그램에 꼭 맞춰서 구현할 필요는 없다.
런타임에 Behavior를 바꾸는 비슷한 패턴으로 전략(Strategy) 패턴이 있다. 전략 패턴은 객체의 외부에서 객채의 행동을 선택할 수 있도록 하는 패턴이다. 코드만 놓고 보면 거의 똑같지만 의도와 목적이 다르고 여기에서 미묘한 차이가 존재한다. 이 예제는 이벤트 핸들러를 구현하는 객체 내부에서 자신의 상태에 따라 이벤트를 처리하는 것이고 이를 외부에서 알 필요는 없으므로 전략 패턴이 아닌 상태 패턴으로 구현하는 것이 적절하다.