일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 코딩
- Baekjoon
- Spring
- Elasticsearch
- 스프링
- Java
- 애자일프로그래밍
- 읽기쉬운코드
- kotlin
- cleancode
- database
- 그리디알고리즘
- 알고리즘
- 애자일기법
- 코드
- 백준
- 개발자
- framework
- API
- 자바
- 코딩테스트
- 엘라스틱서치
- 프레임워크
- ES
- 개발
- spring boot
- 클린코드
- 데이터베이스
- 그리디
- JPA
- Today
- Total
튼튼발자 개발 성장기🏋️
Java Bean to Kotlin Value 본문
Kotlin은 불변 객체를 선호하고 데이터를 표현하는 객체를 다른 유형의 객체보다 더 선호한다.
- 맵의 키나 집합 원소로 불변 객체를 넣을 수 있다.
- 불변 객체의 불변 컬렉션에 대해 이터레이션하는 경우 원소가 달라질지 염려할 필요가 없다.
- 초기 상태를 깊은 복사하지 않고도 다양한 시나리오를 시도할 수 있다.
- 여러 스레드에서 불변 객체를 안전하게 공유할 수 있다.
이러한 이유로 코틀린에서는 [값]이라고 하는 것을 사용할 수 있다.
아래 예제를 통해 쉽게 알아보자.
public class UserPreferences {
private String greeting;
private Locale locale;
private Currency currency;
public UserPreferences() {
this("Hello", Locale.UK, Currency.getInstance(Locale.UK));
}
public UserPreferences(String greeting, Locale locale, Currency currency) {
this.greeting = greeting;
this.locale = locale;
this.currency = currency;
}
public String getGreeting() {
return greeting;
}
public void setGreeting(String greeting) {
this.greeting = greeting;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
public Currency getCurrency() {
return currency;
}
public void setCurrency(Currency currency) {
this.currency = currency;
}
}
// ...
public class Application {
private final UserPreferences preferences;
public Application(UserPreferences preferences) {
this.preferences = preferences;
}
public void showWelcome() {
new WelcomeView(preferences).show();
}
public void editPreferences() {
new PreferencesView(preferences).show();
}
}
// ...
public class PreferencesView extends View {
private final UserPreferences preferences;
private final GreetingPicker greetingPicker = new GreetingPicker();
private final LocalePicker localePicker = new LocalePicker();
private final CurrencyPicker currencyPicker = new CurrencyPicker();
public PreferencesView(UserPreferences preferences) {
this.preferences = preferences;
}
public void show() {
greetingPicker.setGreeting(preferences.getGreeting());
localePicker.setLocale(preferences.getLocale());
currencyPicker.setCurrency(preferences.getCurrency());
super.show();
}
protected void onGreetingChange() {
preferences.setGreeting(greetingPicker.getGreeting());
}
protected void onLocaleChange() {
preferences.setLocale(localePicker.getLocale());
}
protected void onCurrencyChange() {
preferences.setCurrency(currencyPicker.getCurrency());
}
}
class GreetingPicker {
private String greeting;
public String getGreeting() {
return greeting;
}
public void setGreeting(String greeting) {
this.greeting = greeting;
}
}
class LocalePicker {
private Locale locale;
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
}
class CurrencyPicker {
private Currency currency;
public Currency getCurrency() {
return currency;
}
public void setCurrency(Currency currency) {
this.currency = currency;
}
}
Java 코드는 길게 설명하지 않겠다. 위 코드를 보면 아래 내용을 알 수 있다.
- PreferencesView와 WelcomeView가 둘 다 활성화 된 경우 WelcomeView의 상태가 현재 값과 달라질 수 있다.
- UserPreferences의 동등성과 해시 코드가 가변 프로퍼티 값에 따라 결정된다. 그래서 UserPreferences를 집합에 넣거나 맵의 키로 사용할 수 없다.
- WelcomeView가 사용자 설정 정보만을 읽는다는 사실을 알려주는 표시가 없다.
- 읽기와 쓰기가 다른 스레드에서 발생하는 경우 설정 프로퍼티 수준에서 동기화를 처리해야만 한다.
위 예제와 같이 가변 데이터를 사용하면 복잡성이 매우 높다는 것을 알 수 있다. 이런 부분을 불변 값으로 단계 별 리팩토링해보자.
step 1. 간단한 코틀린 클래스로 포팅
java class의 있는 그대로를 단순히 포팅만 해보자.
class Application(
private val preferences: UserPreferences
) {
fun showWelcome() {
WelcomeView(preferences).show()
}
fun editPreferences() {
PreferencesView(preferences).show()
}
}
// ...
class UserPreferences @JvmOverloads constructor(
var greeting: String = "Hello",
var locale: Locale = Locale.UK,
var currency: Currency = Currency.getInstance(Locale.UK)
)
// ...
class PreferencesView(
private val preferences: UserPreferences
) : View() {
private val greetingPicker = GreetingPicker()
private val localePicker = LocalePicker()
private val currencyPicker = CurrencyPicker()
override fun show() {
greetingPicker.greeting = preferences.greeting
localePicker.locale = preferences.locale
currencyPicker.currency = preferences.currency
super.show()
}
protected fun onGreetingChange() {
preferences.greeting = greetingPicker.greeting
}
protected fun onLocaleChange() {
preferences.locale = localePicker.locale
}
protected fun onCurrencyChange() {
preferences.currency = currencyPicker.currency
}
}
internal class GreetingPicker {
var greeting: String = TODO()
}
internal class LocalePicker {
var locale: Locale = TODO()
}
internal class CurrencyPicker {
var currency: Currency = TODO()
}
step 2. 불변을 사용해보자.
UserPreference 클래스를 보면 var를 사용하여 불변이 아닌 가변 데이터로 정의했고, 이는 컴파일러가 property마다 private field, getter, setter를 생성한다는 점을 꼭 알고있어야한다. (참고로 가변 빈은 무조건 안좋은 건 아니고 좋을 때도 있다.) 여기서는 이를 불변으로 만들면 더 좋다.
두괄식으로 이야기 하자면 아래와 같다.
1. 기능의 목적은 사용자가 변경한 내용을 반영해야하는데, 이 변경 시점을의 위치를 바꾼다.
2. 사용자 설정을 수정하는 대신에 Application에서 설정을 가리키는 참조를 변경한다. (불변 참조)
3. 가변 객체에 대한 불변 참조를 불변 객체에 대한 가변 참조로 바꾼다.
위와 같이 PreferenceView 클래스의 show() 함수는 상태 변경을 막아야한다. 그러기 위해서는 변경한 내용을 적용한 UserPreferences 복사본을 리턴하는 버전이 필요하다.
class PreferencesView(
private val preferences: UserPreferences
) : View() {
private val greetingPicker = GreetingPicker()
private val localePicker = LocalePicker()
private val currencyPicker = CurrencyPicker()
fun showModal(): UserPreferences {
greetingPicker.greeting = preferences.greeting
localePicker.locale = preferences.locale
currencyPicker.currency = preferences.currency
show()
return preferences
}
protected fun onGreetingChange() {
preferences.greeting = greetingPicker.greeting
}
protected fun onLocaleChange() {
preferences.locale = localePicker.locale
}
protected fun onCurrencyChange() {
preferences.currency = currencyPicker.currency
}
}
internal class GreetingPicker {
var greeting: String = ""
}
internal class LocalePicker {
var locale: Locale = Locale.UK
}
internal class CurrencyPicker {
var currency: Currency = Currency.getInstance(Locale.UK)
}
Application 클래스에서 editPreferences() 함수를 보면 애플리케이션과 사용자 설정 뷰가 모두 같은 가변 객체에 대한 참조를 공유함으로써 변경된 내용을 얻을 수 있다는 점에 유의해야한다. 이는 preferences를 사변 프로퍼티를 바꾸고 showModal의 결과를 이 프로퍼티에 설정하면 된다.
class Application(
private var preferences: UserPreferences
) {
fun showWelcome() {
WelcomeView(preferences).show()
}
fun editPreferences() {
preferences = PreferencesView(preferences).showModal()
}
}
여기까지만 본다면 가변 데이터에 대한 가변 참조. 가장 안좋은 코드가된다.
*Change() 함수가 호출될 때 preference 프로퍼티에 새로운 UserPreferences 객체를 지정할 수 있게 하기 위해 PreferencesView의 preferences 프로퍼티를 가변으로 만든다. (물론 코드는 더 안좋아지고있다...ㅋ)
class PreferencesView(
private var preferences: UserPreferences
) : View() {
private val greetingPicker = GreetingPicker()
private val localePicker = LocalePicker()
private val currencyPicker = CurrencyPicker()
fun showModal(): UserPreferences {
greetingPicker.greeting = preferences.greeting
localePicker.locale = preferences.locale
currencyPicker.currency = preferences.currency
show()
return preferences
}
protected fun onGreetingChange() {
preferences = UserPreferences(
greetingPicker.greeting,
preferences.locale,
preferences.currency
)
}
protected fun onLocaleChange() {
preferences = UserPreferences(
preferences.greeting,
localePicker.locale,
preferences.currency
)
}
protected fun onCurrencyChange() {
preferences = UserPreferences(
preferences.greeting,
preferences.locale,
currencyPicker.currency
)
}
}
internal class GreetingPicker {
var greeting: String = ""
}
internal class LocalePicker {
var locale: Locale = Locale.UK
}
internal class CurrencyPicker {
var currency: Currency = Currency.getInstance(Locale.UK)
}
그나마 다행인 것은 UserPreferences에 대한 setter 호출 부분이 사라진 것을 볼 수 있다. 이 이야기는 이제 적절한 값을 만들어서 생성자에 프로퍼티를 설정한 후 절대 변경하지 않게, 불변의 성질을 줄 수 있다.
data class UserPreferences(
val greeting: String,
val locale: Locale,
val currency: Currency
)
PreferencesView 클래스에는 여전히 가변 참조가 있는 것이 매우 불편하다. 이 부분은 참조를 하지 않고 showModal() 함수에 사용자 설정을 넘겨보자.
class PreferencesView : View() {
private val greetingPicker = GreetingPicker()
private val localePicker = LocalePicker()
private val currencyPicker = CurrencyPicker()
fun showModal(preferences: UserPreferences): UserPreferences {
greetingPicker.greeting = preferences.greeting
localePicker.locale = preferences.locale
currencyPicker.currency = preferences.currency
show()
return UserPreferences(
greeting = greetingPicker.greeting,
locale = localePicker.locale,
currency = currencyPicker.currency
)
}
}
internal class GreetingPicker {
var greeting: String = ""
}
internal class LocalePicker {
var locale: Locale = Locale.UK
}
internal class CurrencyPicker {
var currency: Currency = Currency.getInstance(Locale.UK)
}
// ...
class Application(
private var preferences: UserPreferences
) {
fun showWelcome() {
WelcomeView(preferences).show()
}
fun editPreferences() {
preferences = PreferencesView().showModal(preferences)
}
}
이제는 사용자 설정이 변경될 수 있는 부분이 한 군데 뿐이고, editPreferences에서 대입문이 하나밖에 없다는 것을 명확하게 알 수 있게 되었다.
'프로그래밍 > Kotlin' 카테고리의 다른 글
Java Collections to Kotlin Collections (1) | 2025.06.15 |
---|---|
Java Optional to Kotlin Nullable Type (1) | 2025.06.04 |
Java Class to Kotlin Class (0) | 2025.06.04 |
[Kotlin] 리스트 (0) | 2024.12.08 |
[Kotlin] 재귀와 공재귀 (0) | 2024.12.07 |