이전 글에서 마지막에 delegation(위임)에 대한 기능에 대해 간략하게 정리했습니다.
이번에는 delegation을 이용한 클래스의 확장에 대해 좀 더 깊게 알아보겠습니다.
상속과 위임
일반적으로 상속과 위임은 공통된 코드를 재사용 할 수 있다는 점은 동일하지만 그 영향력은 매우 다릅니다.
많은 디자인패턴 자료에서 상속보다는 위임을 하라는 이야기를 합니다. 무분별 한 상속의 경우 코드 변경에 대한 영향 범위가 너무 넓기 때문에 너무 많은 단계의 상속은 지양하고 위임을 권한다고 합니다. 심지어 자바의 개발자로 알려진 제임스 고슬링이 최근에 만든 고 언어의 경우 상속이라는 개념 자체를 만들지 않기도 했습니다.
그럼 우리가 개발 할 때 이 둘은 어떻게 구분해야 할까요? 상속은 상속받은 클래스를 부모 클래스로 인식하게 되기 때문에(is) 두 클래스가 같은 종류라면 적용할 수 있을 것이고 위임은 그외의 클래스가 같은 종류는 아니지만 해당 기능을 가지고 있을때(has) 사용 할 수 있습니다.
1 | interface Calculable { |
간단한 예를 들면 컴퓨터와 cpu 는 계산을 할 수 있습니다. 하지만 Cpu와 컴퓨터는 같은 물건이 아닙니다. (Computer has Cpu)
하지만 pc에 들어가는 x86 cpu 와 스마트폰에 주로 들어가는 arm cpu는 둘 다 같은 cpu 로 취급합니다. (X86Cpu is Cpu)
변수와 속성에 대한 위임
코틀린에서는 클래스 간의 위임 뿐 아니라 단순 변수에 대해서도 위임이 가능합니다.
변수에서의 위임
1 | class DelegateString(var str: String = "") { |
오버로딩한 getValue, setValue 연산자는 코틀린에서 변수에 값을 할당하거나 값을 사용할 때 실행되는 연산자 메소드 입니다. 따라서 msg 변수에 값을 할당할 땐 DelegateString 의 setValue 메소드가 호출되고 println 등을 통해 값을 사용할 땐 getValue 가 실행됩니다.
클래스 속성에서의 위임
변수에서 위임하듯이 클래스의 속성으로의 위임도 가능합니다. 이런 경우 다음과 같은 기능을 구현하기 쉽습니다.
- 지연 로딩
- 변화 감지
- 실제 필드를 map 으로 관리
코드는 아래와 같이 작성합니다.
1 | class Test { |
t.p 를 사용할 때 지정된 메소드가 호출 된 것을 확인 할 수 있습니다.
표준 위임
위에서 설명한 기능을 구현하기 위해 코틀린에서 기본적으로 관련된 기능들을 제공합니다.
lazy
lazy 는 Lazy
1 | val v by lazy { test() } |
필요할 때 함수를 사용하고자 하면 그 때 람다가 실행되는 구조를 가집니다.
observable
1 | var v by Delegate.observable("init") { |
observable은 이름 그대로 변수의 변경을 감지하여 실행될 함수를 바인딩 해줍니다. 같이 정의하는 람다는 리턴값이 없는 람다로 위와 같이 구현 할 수 있습니다.
votoable
1 | var v by Delegates.vetoable("init") { |
vetoable은 변경이 일어날 때 람다를 실행시켜 나오는 결과에 따라 데이터 업데이트를 허용할지 말지 결정하는 메소드 입니다. 위 예제에서 값이 비어있지 않으면 변경을 허용하도록 되어 있기때문에, 빈 문자열을 할당하려 했을때 실패하고 실제 “1”을 할당할 때는 정상적으로 값이 반영되었음을 알 수 있습니다.
출처
다재다능 코틀린 프로그래밍
코틀린 공식문서