231225 TIL #277 Kotlin #12 Convection 관례

김춘복·2023년 12월 25일
0

TIL : Today I Learned

목록 보기
277/571

Today I Learned

오늘은 Kotlin in Action에서 관례 부분에 대해 공부했다.


컬렉션과 범위에 관한 관례

  1. get
    x[a, b] -----> x.get(a,b)
    ex) mutableMap[key] -----> mutableMap.get(key)

  2. set
    x.[a, b] = c -----> x.set(a,b,c)
    ex) mutableMap[key] = newValue -----> mutableMap[key].set(newValue)

  3. in
    in은 객체가 컬렉션에 들어있는지 검사한다. a가 c안에 속해있나? == c안에 a가 있나?
    a in c ------> c.contains(a)

  4. .. (rangeTo)
    start..end -----> start.rangeTo(end)

  • for 루프에서 in은 iterator 관례를 따른다. 위의 in과는 다른 용법이다.

구조 분해 선언 (destructing declaration)

구조 분해 선언은 일반 변수 선언과 비슷해보이지만, =의 좌변에 여러 변수들을 괄호로 묶었다는 점에서 다르다.

    val p5 = Point(10,20)
    val (x,y) = p5
    println(x)
    println(y)
  • val (a,b) = p ---> val a = p.component1() / val b = p.component2()

  • data 클래스의 주 생성자에 있는 프로퍼티에 대해서는 컴파일러가 자동으로 componentN 함수를 만들어준다.

data class NameComponents(val name: String, val extension: String)
fun splitFilename(fullName: String): NameComponents {
    val (name, extension) = fullName.split(".", limit = 2) // 구조분해선언 사용
    return NameComponents(name, extension)
}
    
    //...
    val fileName = "example.kt"
    println(splitFilename(fileName)) //NameComponents(name=example, extension=kt)
}
  • 구조 분해 선언은 특히 맵의 원소를 이터레이션할 때 유용하다.
fun printEntries(map: Map<String, String>){
    for ((key,value) in map){
        println("$key -> $value")
    }
}
// ...
    val map = mapOf("K" to "KK", "A" to "AA")
    printEntries(map)
	/*
	K -> KK
	A -> AA
	*/

위임 프로퍼티(delegated property)

Kotlin에서 클래스의 프로퍼티를 효과적으로 대리(delegate)하여 처리할 수 있는 기능

  • 일반적인 문법
    p 프로퍼티는 접근자 로직을 by 키워드를 통해 다른 객체에게 위임한다.
    아래에선 Delegate 클래스의 인스턴스를 위임 객체로 사용한다.
    Delegate 클래스는 getValue와 setValue 메서드를 제공해야 한다.
class Foo {
	var p: Type by Delegate()
}

class Delegate  {
	operator fun getValue(...) {...}
    operator fun setValue(..., value: Type) {...}
}

여기서 Foo의 인스턴스 foo의 foo.p를 사용하면, 위임 프로퍼티 객체에 있는 메서드를 호출한다.

by lazy{ }를 사용한 프로퍼티 초기화 지연

지연 초기화(lazy initialization)은 객체 일부분을 초기화하지 않고 남겨뒀다가 그 값이 필요할 경우 초기화할 때 사용하는 패턴이다.

  • 예를들어 Person 클래스에서 자신이 작성한 이메일 목록을 제공할 때, 이메일이 DB에 들어있으면 불러오는데 시간이 오래 걸린다. 그래서 이메일 프로퍼티를 최초로 사용할 때 단 한번만 DB에서 가져오고 싶으면 지연 초기화를 사용하면 된다.(불러오기 전에는 null 상태)
class Person(val name: String) {
	val emails by lazy { loadEmails(this) }
    // loadEmails는 이메일을 DB에서 불러오는 임의의 함수
}
  • 소스에서 변수가 최초로 이용되는 순간, 중괄호 부분이 자동으로 실행되어 결과값이 변수에 할당된다. 중괄호가 여러줄이면 마지막줄이 변수의 초기값이 된다.

  • lazy 함수는 코틀린 관례에 맞는 시그니처의 getValue 메서드가 있는 객체를 반환한다.
    따라서 lazy를 by 키워드와 함께 사용해 위임 프로퍼티를 만들 수 있다.
    lazy 함수의 인자는 값을 초기화할 때 호출할 람다다.

  • lazy 함수는 기본적으로 스레드 안전하다.


위임 프로퍼티 구현

어떤 객체의 프로퍼티가 바뀔 때마다 리스너에게 변경 통지를 보내는 예시

  • by키워드를 이용해 위임 객체를 지정하면 컴파일러가 작업을 자동으로 처리해준다.
class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
	var age: Int by ObservableProperty(age, changeSupport)
    var salary: Int by ObservableProperty(salary, changeSupport)
}
  • 위임 프로퍼티는 프로퍼티 값이 저장될 장소를 바꿀 수도 있고(맵, DB테이블, 사용자 세션의 쿠키 등), 프로퍼티를 읽거나 쓸 때 벌어질 일을 변경할 수도 있다(값검증, 변경 통지 등).
    이런 일을 모두 간결한 코드로 달성할 수 있다.

프레임워크 사용하는 예시

object Users : idTable() { // 이 객체는 DB 테이블에 해당한다.
	// 프로퍼티는 테이블 컬럼에 해당
	val name = varchar("name", length = 50).index()
    val age = integer("age")
}

class User(id: EntityId) : Entity(id) { // 각 User 인터페이스는 테이블에 들어있는 구체적 Entity에 해당
	var name: String by Users.name // 사용자 이름은 DB의 "name" 컬럼에 있다.
    var age: Int by Users.age
}
  • Users 객체는 DB 테이블이다. DB 전체에 하나만 존재하는 테이블이기 때문에 싱글톤으로 object 객체 선언했다.

  • User의 상위 클래스인 Entity 클래스는 DB 컬럼을 엔티티의 속성값으로 매핑해준다.

  • 이 코드를 통해 User의 프로퍼티에 접근할 때 자동으로 Entity 클래스에 정의된 DB 매핑으로부터 필요한 값을 가져온다.

profile
Backend Dev / Data Engineer

0개의 댓글