일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Observer Pattern
- PrototypePattern
- 추상팩토리패턴
- Kotlin
- builderPattern
- El
- 디자인패턴
- ㅓ
- ㅋㅁ
- factory method
- Functional Programming
- Design Pattern
- F
- 함수형프로그래밍
- a
- 프로토타입 패턴
- 싱글톤
- Abstract Factory
- designPattern
- 빌터패턴
- Singleton
- r
- 옵저버 패턴
- 추상 팩토리
- 코틀린
- 팩토리 메소드
- 디자인패턴 #
- Today
- Total
오늘도 더 나은 코드를 작성하였습니까?
연산자 오버로딩과 기타 관례(프로퍼티 접근자 로직 재활용: 위임 프로퍼티) 본문
코틀린이 제공하는 관례에 의존하는 특성 중에 독특하면서도 가장 강력한 기능인
위임 프로퍼티 delegated property 다.
백킹 필드에 단순히 값을 저장하는 것보다 더 복잡한 방식으로 동작하는 프로퍼티를 쉽게 구현 가능하다.
그 과정에서 접근자 로직을 매번 재 구현할 필요도 없다.
자신의 값을 필드가 아닌 데이터베이스 브라우저 세션 맵 등에 저장할 수 있다.
위임이란 객체가 직접 작업을 수행하지 않고 다른 도우미 객체가 그 작업을 처리하게 하는 디자인 패턴이다.
이런 도우미 객체를 위임객체라 부른다.
by 뒤에 있는 식을 계산해서 위임에 쓰일 객체를 얻는다. 프로퍼티 위임 객체가 따라야 하는 관례를 따르는 모든 객체를 위임에 사용 가능하다.
class Delegate{
operator fun getValue( ){ }
operator fun setValue( ){ }
}
class Foo{
var p: Type by Delegate()
}
class Foo{
// 컴파일러가 아래와 같이 생성함.
private val delegate = Delegates()
val p: Type
set(value: Type) = delegate.setValue(value)
get() = delegate.getValue(...)
}
프로퍼티 위임 관례를 따르는 Delegate 클래스는 getValue() setValue()를 제공해야 된다.
멤버 메서드나 확장 함수 두 형태 모두 가능하다.
위임 프로퍼티 사용 : by lazy()를 사용한 프로퍼티 초기화 지연
지연 초기화는 객체의 일부분을 초기화하지 않고 실제로 그 부분의 값이 필요할 때 초기화할 때 많이 쓰이는 패턴이다.
class Email{ }
fun loadEmails(person: Person): List<Email>{
println("${person.name}의 메일을 가져옴.")
return listOf()
}
class Person(val name: String){
private var _emails: List<Email>? =null
val emails: List<Email>
get() {
if (_emails == null)
_emails = loadEmails(this)
return _emails!!
}
}
fun main() {
val p = Person("Alice")
p.emails
}
뒷받침하는 프로퍼티라는 기법을 이용하여 _emails라는 프로퍼티는 값을 저장하고 다른 프로퍼티인 emails는 _emails라는 프로퍼티에 대한 get연산을 제공한다.
이러한 방법은 지연 초기화해야 될 프로퍼티가 많아지면 코드가 지저분해진다.
또한 이런 방법은 스레드 안전하지 않아서 언제나 제대로 작동하는 것을 기대하기 힘들다.
위임 프로퍼티를 사용하면 훨씬 더 간결해지며 데이터를 저장할 때 사용하는 뒷받침 하는 프로퍼티와 값이 오직 한 번만 초기화됨을 보장한다.
class Person(val name: String){
val emails by lazy { loadEmails(this) }
}
lazy 함수는 코틀린 관례에 맞춰 시그니처의 getValue 메서드가 들어있는 객체를 반환한다.
lazy 함수의 인자는 값을 초기화할 때 호출할 람다다.
lazy 함수는 스레드 안전하다.
필요에 따라 lock을 lazy 함수에 전달할 수도 있고 다중 스레드 환경에서 사용하지 않을 경우 프로퍼티를 위해 lazy함수가 동기화를 하지 못하게 막을 수도 있다.
위임 프로퍼티 구현하기.
1단계
특정 객체를 UI에 표시하는 경우 객체가 바뀌면 자동으로 UI도 바뀌어야 한다.
자바에서는 PropertyChangeSupport와 PropertyChangeEvent 클래스를 사용해서 이런 통지를 처리하는 경우가 자주 있다.
PropertyChangeSupport - 클래스의 리스너 목록을 관리 하며, PropertyChangeEvent (이벤트)가 들어오면 목록의 모든 리스너에게 이벤트를 통지한다.
PropertyChangeSupport의 인스턴스를 저장하고 프로퍼티 변경 시 PropertyChangeSupport에게 처리를 위임하는 방식으로 통지 기능을 주로 구현한다.
PropertyChangeAware ( PropertyChangeSupport 사용을 위한 도우미 클래스 )
open class PropertyChangeAware{
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener){
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener){
changeSupport.removePropertyChangeListener(listener)
}
}
Person (프로퍼티 변경을 인지 하는 클래스 상속)
class Person(val name: String, age: Int, salary: Int): PropertyChangeAware(){
var age: Int = age
set(newValue) {
val oldValue = field
field = newValue
changeSupport.firePropertyChange("age", oldValue, newValue)
}
var salary: Int = salary
set(newValue) {
val oldValue = field
field = newValue
changeSupport.firePropertyChange("salary", oldValue, newValue)
}
}
fun main() {
val p = Person("Alice", 22, 4000)
p.addPropertyChangeListener(
PropertyChangeListener { event ->
println("Property ${event.propertyName} changed from ${event.oldValue} to ${event.newValue}")
}
)
p.age = 28
p.salary = 5000
}
2단계 - 세터 코드를 보면 중복이 많이 보이는걸 제거해 보자
PropertyChangeAware ( PropertyChangeSupport 사용을 위한 도우미 클래스 )
open class PropertyChangeAware{
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener){
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener){
changeSupport.removePropertyChangeListener(listener)
}
}
observableProperty (person의 프로퍼티의 값을 저장 하고 필요에 따라 통지)
class ObservableProperty(
val propName: String, var propValue: Int,
val changeSupport: PropertyChangeSupport
){
fun getValue(): Int = propValue
fun setValue(newValue: Int){
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(propName, oldValue, newValue)
}
}
class Person(val name: String, age: Int, salary: Int): PropertyChangeAware(){
private val _age = ObservableProperty("age", age, changeSupport)
var age: Int
get() = _age.getValue()
set(value) = _age.setValue(value)
private val _salary = ObservableProperty("salayr", salary, changeSupport)
var salary: Int
get() = _salary.getValue()
set(value) = _salary.setValue(value)
}
위 코드는 코틀린의 위임이 실제로 작동하는 방식과 비슷하다.
프로퍼티의 값을 저장하고 그 값을 변경하면 자동으로 변경을 통지를 전달하는 클래스(ObservableProperty)를 만들었고
로직의 중복을 상당 부분 제거하였다.
3단계 ObservableProperty를 코틀린의 관례에 맞도록 수정
각각의 프로퍼티 마다 ObservableProperty를 생성하고 게터와 세터에서 작업을 위임하는 준비 코드가 상당 부분 남아있다.
class ObservableProperty(var propValue: Int, val changeSupport: PropertyChangeSupport){
operator fun getValue(p: Person, prop: KProperty<*>): Int = propValue
operator fun setValue(p: Person, prop: KProperty<*>, newValue: Int){
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
}
class Person(val name: String, age: Int, salary: Int): PropertyChangeAware(){
var age: Int by ObservableProperty(age, changeSupport)
var salary: Int by ObservableProperty(salary, changeSupport)
}
- 코틀린 위임 프로퍼티 관례를 따라 operator를 getValue, setValue에 붙여준다.
- getValue, setValue는 프로퍼티가 포함된 객체(Person)와 프로퍼티를 표현하는 객체(prop: KProperty <*>)를 파라미터로 받는다.
- KProperty인자를 통해 프로퍼티 이름을 전달받기 때문에 ObservableProperty의 주 생성자의 propName을 삭제한다.
by 키워드를 통해 위임 객체를 지정하면 이전 예제에서 직접 코드를 짜야했던 여러 작업을 코틀린 컴파일러가 자동으로 처리해준다.
코틀린은 위임 객체(ObserableProperty)를 감춰진 프로퍼티(컴파일러 생성해줌)에 저장해주고 주 객체(person)의 프로퍼티를 읽거나 쓸 때마다 위임 객체(ObserableProperty)의 getValue setValue를 호출해준다.
4단계 - 관찰 가능한 프로퍼티 로직을 직접 작성하는 대신 코틀린 표준 라이브러리를 사용
ObservableProperty와 비슷한 클래스(Delegates.observable)가 있다.
다만, 이 표준 라이브러리 클래스는 PropertyChangeSupport와 연결되어있지 않아서 프로퍼티 값을 변경을 통지할 때 PropertyChangeSupport를 사용하는 방법을 알려주는 람다를 넘겨줘야 된다.
open class PropertyChangeAware{
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener){
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener){
changeSupport.removePropertyChangeListener(listener)
}
}
class Person(val name: String, age: Int, salary: Int): PropertyChangeAware(){
//위의 ObservablePropery를 대신함, 단 setValue호출시 로직을 람다로 넣어줌
private val observer = {
prop: KProperty<*>, oldValue: Int ,newValue: Int ->
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
var age: Int by Delegates.observable(age, observer)
var salary: Int by Delegates.observable(salary, observer)
}
by 우항에는 반드시 새 인스턴스를 만들 필요는 없다. 함수 호출, 다른 프로퍼티, 다른 식이 올 수 있는데,
식을 계산한 결과가 새 객체일 경우 컴파일러가 호출할 수 있는 getValue setValue를 반드시 제공해야 된다.
'Kotlin in Action > 코틀린 답게 사용하기' 카테고리의 다른 글
연산자 오버로딩과 기타 관례(위임 프로퍼티 기타) (0) | 2020.08.25 |
---|---|
연산자 오버로딩과 기타 관례(위임 프로퍼티 기타) (0) | 2020.08.25 |
연산자 오버로딩과 기타 관례(구조 분해 선언과 component 함수) (0) | 2020.08.24 |
연산자 오버로딩과 기타 관례(컬렉션과 범위에 대해 쓸 수 있는 관례) (0) | 2020.08.24 |
연산자 오버로딩과 기타 관례(비교 연산자 오버로딩) (0) | 2020.08.22 |