Effective Kotlin - Properties should represent state, not behavior

2021. 8. 19. 00:43Computer

코틀린의 속성은 자바의 필드와 비슷해 보입니다. 그렇지만, 서로 다른 컨셉을 갖고 있습니다.

//Kotlinproperty
var name:String?=null 

//Javafield
String name=null;

같은 방식으로 사용할지라도, 코틀린의 속성은 좀 더 많은 것을 할 수 있다.

property 선언에 대한 전체 구문

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

Custom Get, Set ( var )

var name: String? = null
    get() = field?.toUpperCase() 
    set(value) { if(!value.isNullOrBlank()) { field = value } 

Custom Get ( val )

val fullName: String? 
    get() = "$name $surname"

Backing Fields

필드는 커스터마이징하게 선언할 수 없습니다. 다만, 속성에 지원 필드가 필요할경우 자동으로 생성합니다. 이 지원 필드는 field 식별자를 사용하여 접근자에서 참조할 수 있습니다.

var counter = 0 // the initializer assigns the backing field directly
    set(value) {
        if (value >= 0)
            field = value
            // counter = value // ERROR StackOverflow: Using actual name 'counter' would make setter recursive
    }

Different with function?

코틀린에서의 프로퍼티는 해당 객체의 상태를 의미한다면, 함수는 해당 객체의 행위를 의미합니다.

// property
val isEmpty: Boolean
  get() = amount == 0
// function
fun isEmpty(): Boolean {
  return amount == 0
}

생각해봅시다.

항상 속성을 이용하는 방식이 좋을까요?

class FruitBucket(
    val price: Int,
    val discount: Int,
    var amount: Int
) {
  val isEmpty: Boolean
    get() = amount == 0
  val salePrice: Int
    get() = price - discount
}    

다음의 코드를 개선을 한다면 다음처럼 개선을 할 수 있습니다.
접근할때마다 매번 연산을 하는것은 비효율 적이기 떄문이지요.

class FruitBucket(
    val price: Int,
    val discount: Int,
    var amount: Int
) {
  val isEmpty: Boolean
    get() = amount == 0
  val salePrice: Int = price - discount
}

데이터를 보유하지 않은채, 해당 속성을 사용할 수 있다.

어떤 연유로, date 유형을 사용하지 못하는 경우에도, 코틀린의 속성 기능을 사용해서, Date유형의 데이터를 저장하는 것이 아니라, 다른 값을 이용해서, Date 유형의 값을 사용하 수 있다. ( 직렬화등을 할 경우, millis 에 대해서만 값을 갖고 있다가, 사용할때는, Date 형태로 변환하는 형태 )

var date: Date
    get() = Date(millis)
    set(value) {
        millis = value.time
    }

속성은 단순 필드가 아닌, 접근자로서도 사용할 수 있습니다.

valContext.preferences:SharedPreferences 
    get() = PreferenceManager.getDefaultSharedPreferences(this)
valContext.inflater:LayoutInflater 
    get() = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
valContext.notificationManager:NotificationManager 
    get() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

속성기능은 만능일까요?

순환, 반복 등과 같은 특정한 로직이 들어가거나, 알고리즘적인 부분이 주입된 속성은 올바른 사용법이 아닙니다.

// Don't do this !
val Tree<Int>.sum:Int 
    get() = when (this) {
        is Leaf -> value
        is Node -> left.sum + right.sum
    }

Property (속성)은 일반적으로 상태를 나타내거나, 설정하는 데에만 사용해야합니다.
속성을 사용하기보다 함수를 사용하길 권장하는 예시를 드립니다.

  • O(1) 보다 복잡도가 높을 경우 ( 계산 비용이 많이 들경우 )
  • 단순 작업 (로깅, 요소 업데이트 등) 이상의 비즈니스 로직을 담을 경우
  • 멤버를 두 번 연속 호출시, 다른 결과가 출력될 경우.
  • Int.toDouble() 과 같이 관례적 표현의 중복이 생길 경우.
  • Getter 에서 속성의 상태를 변경할 경우.

참고

반응형