코틀린 스터디/이펙티브 코틀린

아이템16) 프로퍼티는 동작이 아니라 상태를 나타내야 한다

막이86 2024. 4. 17. 10:38
728x90
  • 코틀린의 프로퍼티는 자바의 필드와 비슷해보이지만 서로 다른 개념
// 코틀린의 프로퍼티
var name: String? = null

// 자바의 필드
String name = null;
  • 둘다 데이터를 저장
  • 코틀린 프로퍼티에는 더 많은 기능이 있음
    • 사용자 정의 세터와 게터를 가질 수 있음
    var name: String? = null
    	get() = field?.toUpperCase()
    	set(value) {
    		if (!value.isNullOrBlank()) {
    			field = value
    		}
    	}
    
  • field 식별자는 프로퍼티의 데이터를 저장해 두는 백킹 필드에 대한 레퍼런스
    • 세터와 게터의 디폴트 구현에 사용되므로, 따로 만들지 않아도 디폴트로 생성
    • val을 사용해서 읽기 전용 프로퍼티를 만들 대는 field가 만들어지지 않음
    val fullName: String
    	get() = "$name $surname"
    
  • var를 사용해서 만든 읽고 쓸 수 있는 프로퍼티는 게터와 세터를 정의할 수 있음
    • 파생 프로퍼티라고 부르며 자주 사용됨
  • 자바 표준 라이브러리 Date를 활용해 객체에 날짜를 저장해서 많이 활용한 상황
    • 직렬화 문제 등으로 객체를 더 이상 이러한 타입으로 저장할 수 없음
    • 데이터를 millis라는 별도의 프로퍼티로 옮기고, 이를 활용해서 date 프로퍼티에 데이터를 저장하지 않고, 랩/언랩 하도록 코드를 변경하면 됨
    var date: Date
    	get() = Date(millis)
    	set(value) {
    		millis = value.time
    	}
    
  • 프로퍼티는 필드가 필요 없음
    • 프로퍼티는 개념적으로 접근자(val은 게터, var은 게터와 세터)를 나타냄
  • 코틀린은 인터페이스에도 프로퍼티를 정의할 수 있음
interface Person {
	val name: String
}
  • 인터페이스를 오버라이드 할 수 있음
open class Supercomputer {
	open val theAnswer: Long = 42
}

class AppleComputer: Supercomputer() {
	override val theAnswer: Long = 1_800_276_2273
}
  • 프로퍼티를 위임할 수 있음
    • 아이템 21에서 자세히 설명
    val db: Database by lazy { connectToDb() }
    
  • 프로퍼티는 본질적으로 함수이므로, 확장 프로퍼티를 만들 수 있음
val Context.preferences: SharedPreferences
	get() = PreferenceManager.getDefaultSharedPerferences(this)

val Context.inflater: layoutInflater
	get() = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater

val Context.notificationManager: Notificationmanager
	get() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
  • 프로퍼티로 다음과 같이 알고리즘의 동작을 나타내는 것은 좋지 않음
    • 이런 프로퍼티는 여러 가지 오해를 불러일으킬 수 있음
    var Tree<Int>.sum: Int
    	get() = when (this) {
    		is Leaf -> value
    		is Node -> left.sum + right.sum
    	}
    
  • 프로퍼티가 아닌 함수로 구현해야 함
var Tree<Int>.sum: Int
	get() = when (this) {
		is Leaf -> value
		is Node -> left.sum + right.sum
	}
  • 어떤 것을 프로퍼티로 해야하는지 판단할 수 있는 질문
    • 이 프로퍼티를 함수로 정의할 경우, 접두사로 get 또는 set을 붙일 것인가?
    • 아니라면 프로퍼티로 만드는 것이 좋지 않음
  • 프로퍼티 대신 함수를 사용하는 것이 좋은 경우
    • 연산 비용이 높거나 복잡도가 O(1)보다 큰 경우
      • 연산 비용이 많이 들어간다면 함수를 사용하는 것이 좋음
      • 사용자가 연산 비용을 예측하기 쉽고, 이를 기반으로 캐싱 등을 고려할 수 있음
    • 비즈니스 로직을 포함하는 경우
      • 코드를 읽을 때 프로퍼티가 로깅, 리스너 통지, 바인드된 요소 변경과 같은 단순한 동작 이상을 할 거라고 기대하지 않음
    • 결정적이지 않은 경우
      • 같은 동작을 연속적으로 두 번 했는데 다른 값이 나올 수 있는 경우 함수가 좋음
    • 변환의 경우
      • 변환을 프로퍼티로 만들면 오해를 불러 일으킬 수 있음
    • 게터에서 프로퍼티의 상태 변경이 일어나야 하는 경우
      • 게이터에서 프로퍼티의 상태 변화를 일으킨다고 생각하지 않음
  • 요소의 합계를 계산하려면, 모든 요소를 더하는 반복 처리가 필요
    • 선형 복잡도를 가지므로, 프로퍼티가 아니라 함수로 정의하는 것이 좋음
    val s = (1..100).sum()
    
  • 상태를 추출/설정할 때는 프로퍼티를 사용해야 함
// 이렇게 하지 마세요!!
class UserIncorrect {
	private var name: String = ""

	fun getName() = name

	fun setName(name: String) {
		this.name = name
	}
}

class UserCorrect {
	var name: String = ""
}
728x90