일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- rxjava disposable
- rxjava hot observable
- 큐 자바 코드
- 멤버변수
- 안드로이드 레트로핏 crud
- 스택 큐 차이
- 안드로이드 유닛 테스트
- 2022 플러터 설치
- 객체
- 안드로이드 유닛 테스트 예시
- 2022 플러터 안드로이드 스튜디오
- ar vr 차이
- 서비스 쓰레드 차이
- ANR이란
- 클래스
- jvm이란
- 안드로이드 라이선스
- 서비스 vs 쓰레드
- 스택 자바 코드
- 안드로이드 유닛테스트란
- android retrofit login
- android ar 개발
- 자바 다형성
- 플러터 설치 2022
- rxjava cold observable
- 안드로이드 라이선스 종류
- 안드로이드 레트로핏 사용법
- Rxjava Observable
- 안드로이드 os 구조
- jvm 작동 원리
- Today
- Total
나만을 위한 블로그
[이펙티브 코틀린] 아이템 16. 프로퍼티는 동작이 아닌 상태를 나타내야 한다 본문
코틀린의 프로퍼티는 자바의 필드와 비슷해 보이지만 서로 완전히 다른 개념이다.
var name: String? = null
String name = null
데이터를 저장한다는 건 같다. 하지만 프로퍼티엔 더 많은 기능이 있다. 기본적으로 프로퍼티는 사용자 정의 게터, 세터를 가질 수 있다.
var name: String? = null
get() = field?.toUpperCase()
set(value) {
if (!value.isNullOrBlack()) {
field = value
}
}
field는 프로퍼티의 데이터를 저장해 두는 backing field에 대한 레퍼런스다. 이런 backing field는 게터, 세터의 디폴트 구현에 쓰이므로 따로 만들지 않아도 디폴트로 생성된다. val을 써서 읽기 전용 프로퍼티를 만들 때는 field가 만들어지지 않는다.
val fullName: String
get() = "$name surname"
var을 써서 만든 읽고 쓸 수 있는 프로퍼티는 게터, 세터를 정의할 수 있다. 이런 프로퍼티를 파생 프로퍼티(derived property)라고 부르며 자주 쓰인다.
코틀린의 모든 프로퍼티는 디폴트로 캡슐화되어 있다. 자바 표준 라이브러리 Date를 써서 객체에 날짜를 저장해 활용한 상황을 가정한다. 프로젝트 진행 중 직렬화 문제 등으로 객체를 더 이상 이런 타입으로 저장할 수 없게 됐는데 이미 프로젝트 전체에서 이 프로퍼티를 많이 참조하고 있다면 어떻게 해야 할까?
코틀린은 데이터를 millis란 별도 프로퍼티로 옮기고 이를 활용해서 date 프로퍼티에 데이터를 저장하지 않고 wrap, unwrap하도록 코드를 바꾸기만 하면 된다.
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_275_2273
}
같은 이유로 프로퍼티를 위임할 수도 있다.
val db: DataBase by lazy { connectToDb() }
프로퍼티는 본질적으로 함수이므로 확장 프로퍼티를 만들 수도 있다.
val Context.Preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(this)
val Context.inflater: LayoutInflater
get() = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val Context.notificationManager: NotificationManager
get() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
프로퍼티는 필드가 아닌 접근자를 나타낸다. 이처럼 프로퍼티를 함수 대신 쓸 수도 있지만 완전히 대체해서 쓰는 건 좋지 않다. 예를 들어 프로퍼티로 알고리즘 동작을 나타내는 건 좋지 않다.
val Tree<Int>.sum: Int
get() = when (this) {
is Leaf -> value
is Node -> left.sum + right.sum
}
sum 프로퍼티는 모든 요소를 반복 처리하므로 알고리즘 동작을 나타낸다고 할 수 있다. 이런 프로퍼티는 여러 오해를 불러일으킬 수 있다. 큰 컬렉션의 경우 답을 찾을 때 많은 계산량이 필요하다. 하지만 관습적으로 이런 게터에 그런 계산량이 필요하다고 예상하진 않는다. 따라서 이런 처리는 프로퍼티가 아닌 함수로 구현해야 한다.
fun Tree<Int>.sum(): Int = when (this) {
is Leaf -> value
is Node -> left.sum() + right.sum()
}
원칙적으로 프로퍼티는 상태를 나타내거나 설정하는 목적으로만 쓰는 게 좋고 다른 로직 등을 포함하지 않아야 한다. 어떤 걸 프로퍼티로 해야 하는지 판단할 수 있는 간단한 질문이 있다. '이 프로퍼티를 함수로 정의할 경우 접두사로 get 또는 set을 붙일 것인가?' 아니라면 프로퍼티로 만드는 건 좋지 않다. 좀 더 구체적으로 프로퍼티 대신 함수를 쓰는 게 좋은 경우를 정리하면 아래와 같다.
- 연산 비용이 높거나 복잡도가 O(1)보다 큰 경우
- 비즈니스 로직(앱의 동작)을 포함하는 경우
- 결정적이지 않은 경우
- 변환의 경우
- 게터에서 프로퍼티의 상태 변경이 일어나야 하는 경우
요소의 합계를 계산하려면 모든 요소를 더하는 반복 처리가 필요하다. 어떤 처리가 실질적으로 이뤄지므로 상태가 아닌 동작이다. 선형 복잡도를 갖기 때문에 함수로 정의하는 게 좋다.
반대로 상태를 추출 / 설정할 때는 프로퍼티를 써야 한다. 특별한 이유가 없다면 함수를 쓰면 안 된다. 많은 사람은 경험적으로 프로퍼티는 상태 집합을 나타내고 함수는 행동을 나타낸다고 생각한다.
'책 > Effective Kotlin' 카테고리의 다른 글
[이펙티브 코틀린] 아이템 18. 코딩 컨벤션을 지켜라 (0) | 2022.08.07 |
---|---|
[이펙티브 코틀린] 아이템 17. 이름 있는 아규먼트를 사용하라 (0) | 2022.08.07 |
[이펙티브 코틀린] 아이템 15. 리시버를 명시적으로 참조하라 (0) | 2022.07.18 |
[이펙티브 코틀린] 아이템 14. 변수 타입이 명확하지 않은 경우 확실하게 지정하라 (0) | 2022.07.18 |
[이펙티브 코틀린] 아이템 13. Unit?을 리턴하지 마라 (0) | 2022.07.10 |