Kim-Baek 개발자 이야기

자바에서 코틀린 - 5장 빈에서 값으로 본문

개발/java basic

자바에서 코틀린 - 5장 빈에서 값으로

김백개발자 2023. 4. 13. 17:26

POJO?

Plain Old Java Object

public class CoffeePOJO {

   public String name;
   private List<String> ingredients;

   public CoffeePOJO(String name, List<String> ingredients) {
       this.name = name;
       this.ingredients = ingredients;
   }

   void addIngredient(String ingredient) {
       ingredients.add(ingredient);
   }
}

자바 빈(Java Bean)

공식 자바 빈 문서에 따르면 자바 빈은 아래의 조건을 모두 충족하는 POJO이다. (DTO라고도 한다함)

  1. 모든 객체 변수는 Private 제한자를 가지며 getter 와 setter 함수를 통해서 접근 가능하다.
  2. 아무런 입력값을 받지 않는 생성자(constructor)가 존재하여야 한다.
public class CoffeeBEAN implements Serializable {

   private String name;
   private List<String> ingredients;

   public CoffeeBEAN() {
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public List<String> getIngredients() {
       return ingredients;
   }

   public void setIngredients(List<String> ingredients) {
       this.ingredients = ingredients;
   }
}
  • 책에서 말하는 값 이란 불변데이터를 말함
    • 불변 객체의 장점
      • 스레드 세이프
      • copy시 안정성
      • 맵 같은 곳에서 쓰기 좋음
        • 키가 변경되면 동작 보장 x
package travelator.mobile;

import java.util.Currency;
import java.util.Locale;

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;
    }
}
  • 설정 정보
package travelator.mobile;

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();
    }
}
  • UserPreferences로 웰컴화면이나 에디트 화면을 준다.
package travelator.mobile;

import java.util.Currency;
import java.util.Locale;

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;
    }
}
  • 값을 변경한다.
  • 두 개의 뷰가 활성화 된 경우 welcomeView 의 상태가 현재 값과 달라질 수 있다.

코틀린으로 변환

package travelator.mobile

class Application(
		//가변 객체에 대한 불변 참조
    private val preferences: UserPreferences // 변경 불가 value
) {
    fun showWelcome() {
        WelcomeView(preferences).show()
    }

    fun editPreferences() {
        PreferencesView(preferences).show()
    }
}

5.5

package travelator.mobile

import java.util.*

class UserPreferences @JvmOverloads constructor(
    var greeting: String = "Hello", // 변경가능
    var locale: Locale = Locale.UK,
    var currency: Currency = Currency.getInstance(Locale.UK)
)
  • @JvmOverloads 를 통해서 여러 생성자르 만들 수 있음
    • 비공개로 getter / setter 다 있음

5.6

package travelator.mobile

import java.util.*

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()
}

5.7

package travelator.mobile

import java.util.*

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)
}
  • 원본 객체의 상태를 막기 위해서, 변경한 내용을 갖는 복사본을 돌려주게 할 것이다.
    • 현재는 기존의 값을 동일하게 리턴한다.

5.8

package travelator.mobile

class Application(
		//가변 객체에 대한 불변 참조
    private val preferences: UserPreferences // 변경 불가 value
) {
    fun showWelcome() {
        WelcomeView(preferences).show()
    }

    fun editPreferences() {
        PreferencesView(preferences).show()
    }
}

=============================================

class Application(
		// 가변 객체에 대한 가변 참조로 변경
    private var preferences: UserPreferences // <1>
) {
    fun showWelcome() {
        WelcomeView(preferences).show()
    }

    fun editPreferences() {
        preferences = PreferencesView(preferences).showModal()
    }
}
  • 복사본을 줄 것이기 때문에, 불변 참조가 아닌 가변 참조로 바꾼다. ( val → var )

5.9

package travelator.mobile

import java.util.*

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 가 사라짐
package travelator.mobile

import java.util.*

data class UserPreferences(
    val greeting: String,
    val locale: Locale,
    val currency: Currency
)
  • 기존의 값을 바꾸지 않으니, UserPreferences를 data 클래스로 바꾼다.
    • val (value) 로 프로퍼티가 있으면 setter가 생성 안됨

5.11

package travelator.mobile

import java.util.*

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)
}
  • 프로퍼티에 가변 참조로 두는것도 별로니, 참조를 제거한다.

5.12

class Application(
		//가변 객체에 대한 불변 참조
    private val preferences: UserPreferences // 변경 불가 value
) {
    fun showWelcome() {
        WelcomeView(preferences).show()
    }

    fun editPreferences() {
        PreferencesView(preferences).show()
    }
}

=============================================

class Application(
		// 가변 객체에 대한 가변 참조로 변경
    private var preferences: UserPreferences // <1>
) {
    fun showWelcome() {
        WelcomeView(preferences).show()
    }

    fun editPreferences() {
        preferences = PreferencesView(preferences).showModal()
    }
}

=============================================

class Application(
    private var preferences: UserPreferences
) {
    fun showWelcome() {
        WelcomeView(preferences).show()
    }

    fun editPreferences() {
        preferences = PreferencesView().showModal(preferences)
    }
  • 가변 객체의 위험을 더 윗단으로 올린다.
    • 나머지를 다 불변으로 만들어서, 한곳에서 관리하도록 한다.
  • 가변 객체에 대한 불변 참조 → 불변 객체에 대한 가변 참조로 변경!
반응형
Comments