[Kotlin] Kotlin식 문법: 클래스, 객체, 인터페이스 -5

박준규·2022년 2월 21일
0

코틀린

목록 보기
14/19
post-custom-banner

인터페이스에 선언된 프로퍼티 구현

kotlin에서는 interface에 추상 property를 넣을 수 있다. 이 역시 구현체는 아니다. 다음가 같이 코드를 작성하면 추상 property를 포함한 interface를 구현할 수 있다.

interface User {
    val nickname: String
}

이러한 interface를 만들면 구현체인 class 내부에서 nickname이라는 property를 얻을 수 있는 방법을 제공해야 한다. 이때 중요한 것은 interface에 있는 property 선언에는 뒷받침하는 필드나 게터 등의 정보가 들어있지 않다.

너무나 자명하듯이 사실 interface는 아무 상태도 포함할 수 없으므로 상태를 저장할 필요가 있다면 interface를 구현한 sub class에서 상태 저장을 위한 property를 만들어야 한다.

이제 User라는 interface를 구현해보자. 작성할 구현체는 3개로 각각의 class는 interface의 추상 property를 각각의 방식대로 구현한다.

interface User {
    val nickname: String
}

class PrivateUser(override val nickname: String) : User

class SubscribingUser(val email: String) : User {
    override val nickname: String
    	get() = email.substringBefore('@')
}

class FacebookUser(val account: Int): User {
    override val nickname = getFaceBookName(accountId)
}
  1. PrivateUser class의 경우 주 생성자 안에 nickname을 override 하고 default 파라미터로 선언하는 간결한 구문을 사용했다.
  2. SubscribingUser의 경우 새로운 주 생성자를 받으면서 nickname이라는 값을 얻기 위해 get()을 사용하여 구체화하였다.
  3. FacebookUser의 경우 accountId를 주 생성자로 생성하고 이를 통해 nickname을 얻는 방식을 구현했다.

물론 FacebookUser의 경우 그냥 코드를 run시키면 동작하지 않는다. 왜냐하면 Facebook에 직접 접속하여 데이터를 갖고 오는 것이기 때문에 필요한 dependency를 gradle이나 maven에 추가해야 한다.

그리고 이러한 방식은 생각보다 비용이 많이 들수도 있다. 따라서 객체를 초기화 하는 단계에서 한 번만 getFacebookName을 호출하도록 설계했다.

그러면 여기서 중요한 것은 SubscribingUser의 경우 매번 해당 nickname값을 얻기 위해서 nickname을 호출할 때 마다 substringBefore를 호출한다. 하지만 FacebookUser의 경우 데이터를 필드에 저장했다가 불러오는 방식을 사용한다.

또한 interface에는 추상 property뿐만 아니라 getter와 setter가 있는 property를 선언할 수도 있다. 물론 getter와 setter는 뒷받침하는 필드를 참조할 수 없다. 아래의 코드를 살펴보자

interface User {
    val email: String
    val nickname: String
    	get() = email.substringBefore('@')
}

위에서 get()은 property에 뒷받침하는 필드가 없다. 대신 매번 결과를 계산해 돌려준다. (매번 계산을 하지 않기 위해서는 field가 필요하다. 근데 interface는 뒷받침하는 필드를 놓을 수 없다.)

그럼 어떻게 해야 하지..?

사실 값을 저장하면서 동시에 로직을 실행할 수 있도록 하기 위해서는 접근자 안에서 property를 뒷받침하는 필드에 접근할 수 있어야 한다.

다음의 예제는 property에 저장된 값의 변경 이력을 로그에 남기려는 경우를 생각해보자. 이때는 변경 가능한 프로퍼티를 정의하되 setter에서 property 값을 바꿀 때 마다 약간의 코드를 추가로 실행해야 한다.

class User(val name: String) {
    var address: String = "unspecified"
    	set(value: String) {
            println("""Address was changed for $name: "$field" -> "$value".""".trimIndent())
            field = value
        }
}

fun main() {
    val user = User("Junkyu")
    user.address = "서울특별시 어디가에 있는 1, 88888 영등포"
    // Address was changed for Junkyu: "unspecified" -> "서울특별시 어디가에 있는 1, 88888 영등포".
}

위 예제에서는 field라는 특별한 식별자를 통해 뒷받침하는 필드에 접근할 수 있다. getter에서는 field 값을 읽을 수만 있고, 세터에서는 field값을 읽거나 쓸 수 있다.

변경 가능 프로퍼티의 getter와 setter 중 한쪽만 직접 정의해도 된다는 점을 기억해야 한다.

물론 위 예제에서 address의 getter는 field값을 반환해주는 뻔한 getter이다. kotlin에서는 이렇게 단순한 getter의 경우 자동으로 생성해주기 때문에 굳이 코드를 작성할 필요가 없었으므로, 직접 정의할 필요가 없다.

그렇다면 뒷받침하는 필드와 그런 필드가 없는 property에 어떤 차이가 있는 건가?

클래스의 property를 사용하는 쪽에서 property를 읽는 방법이나 쓰는 방법은 뒷받침하는 field의 유뮤와는 관계가 없다.

profile
'개발'은 '예술'이고 '서비스'는 '작품'이다
post-custom-banner

0개의 댓글