[Effective Kotlin] 아이템 34.기본 생성자에 이름있는 옵션 아규먼트를 사용하라

0

객체를 정의하고 생성하는 가장 기본적인 방법은 기본 생성자(primary constructor)를 사용하는것이다.

class User(var name : String , var surname : String)
val user = User("Monkey", "D 루파")

기본생성자를 활용해서 객체를 만드는것이 좋다.

데이터 클래스의 객체를 생성하거나, 객체의 종속성을 주입할수도 있다.

//데이터 모델을 데터 클래스로 사용하면, 프로젝트 내부의 데이터로 사용된다는것을 명확하게 알수있고,
//데이터로 활용할때, 유용한 몇가지 함수를 제공한다.
data class Student(
val name : String,
val surname : String,
val age : Int
)
class QuotationPresenter(
 private val view: QuotationView,
 private val repo: QutationRepository
){
// 기본생성자 선언된거 외에도 더많은 프로퍼티를 갖고있지만 생성자로 OK.
	private var nextQuoteId = -1

	fun onStart(){
 		onNext()
	}

	fun onNext() {
 		nextQuoteId = (nextQuoteId + 1) % repo.quotoNumber
 		val quoto = repo.getQuote(nextQuoteId)
    	view.showQuoto(quote)
	}
}

기본생성자가 좋은 방식인 이유를 이해하려면, 아래와 관련된 자바패턴을 이해하는것이 좋다.

  • 점층적 생성자 패턴
  • 빌더 패턴

점층적 생성자 패턴

점층적 생성자 패턴이란 '여러가지 종류의 생성자를 사용하는' 아주 간단한 패턴이다.

class Pizza{
	val name : String
    val cheese : Int
    val olview : Int
    val bacon : Int
}

constructor(size: String , cheese : Int , olives : Int , bacon : Int){
 this.size = size
 this.cheese = cheese
 this.olives = olives
 this.bacon = bacon
 }
constructor(size: String , cheese : Int,olives : Int): this (size, cheese, 0)
constructor(size: String , cheese : Int): this (size, 0 )

위와같이 사용하는것은 좋은코드가 아니다.

코틀린에서는 디폴트 아규먼트를 사용한다.

class Pizza(
	val size: String,
    val chees : Int = 0
    val olives : Int = 0
    val bacon :Int = 0

)

디폴트 아규먼트를 사용할경우, 장점이 생기는데

  • 파타미터들의 값을 원하는대로 지정가능
  • 아규먼트를 원하는 순서로 지정 가능
  • 명시적으로 이름을 붙일수 있다.
val villagePizza = Pizza(
 size = "L"
 cheese = 1,
 olives = 2,
 bacon = 3
)

빌더패턴

자바는 코틀린과 달리 디폴트 아규먼트나 이름있는 파라미터를 사용할수 없다.
그래서 자바에서는 빌더 패턴을 사용한다.

  • 디폴트값을 지정가능
  • 아규먼트를 원하는 순서로 지정 가능
  • 파라미터에 명시적으로 이름을 붙일수 있다.
class Pizza private constructor(
	val name : String
    val cheese : Int
    val olives : Int
    val bacon : Int
){
	class Builder(private size : String){
    	private var cheese : Int = 0
    	private var olives : Int = 0
        
    fun setChesse(value :Int) : Builder = apply {chees = value}
    fun setOlvies(value :Int) : Builder = apply {chees = value}
    
   	fun build () = Pizza(size, chees, olives, bacon)
    }
    

}

빌더 패턴보다 디폴트 파라미터와 이름있는 파라미터를 사용하는것이 좋은이유는 무엇일까?

  • 더 짧다.
    읽는입장에서도 읽기 쉽고, 구현하는입장에서 빌더패턴은 구현하는데 시간이 많이걸린다.

  • 더 명확하다.
    거대하게 빌더패턴으로 만들어진 객체는 어떤 추가적인 처리를 하는지 이해하기 어렵다.

  • 사용하기 쉽다.
    빌더패턴은 언어위체 추가로 구현한 개념이므로 추가적인 공부가 필요하다.

  • 동시성과 관련한 문제가 없다.
    빌더 패턴은 일반적인 mutable이므로 thread Safety 하게 구현하기 어렵다.

그렇다면 빌더패턴이 좋은 경우를 간단하게 실펴보자.
빌더 패턴은 값의 의미를 묶어서 지정할수도 있고, 특정값을 누적시킬수도 있다.

//값의 의미를 묶을수 있다.
val dialog = AlertDialog.Builder(context)
.setMassage(R.string.Fire_missiale)
.setPositiveButton(R.string.fire , {d, id ->
	//미사일 발사
})

//addRoute를 누적
val router = Router.Builder()
.addRoute(path = "/home" , ::showHome)
.addRoute(path = "/user" , ::showUser)

빌더패턴을 사용하지 않을경우, 추가적인 타입을 만들어야하기때문에 코드가 복잡해진다.

일반적으로 이런코드는 DSL 빌더를 사용한다.

val dialog = context.alret(R.string.fire_missiles){
 positveButton(R.stirng.fire){
 
 }
 negativeButton(){
 
 }
}

val route = router {
	"/home" directsTo ::showHome
	"/users" directsTo ::showUsers
    
}

이렇게 DSL 빌더를 활용하면, 빌더패턴보다 훨씬 유연하고 명확하다.

또 빌더패턴의 장점으로는 팩토리를 사용할 수 있다.

fun Context.makeDefaultDialogBuilder = AlertDialog.Builder(this)
	.setIcon(R.drawable.ic_Dialog)
	.setTitle(R.string.dialog_title)
    .setOnCancelListenr{it.cancel}
    

팩토리를 사용하려면, 커링(currying)을 사용해야하나, 코틀린은 커링을 지원하지 않는다.

대신 객체를 데이터클래스로 만들고, 이를 copy로 복제해서 필요한 설정을 수정해서 사용한다.

data class DialogConfig(
	val icon: Int = -1,
	val title: Int = -1,
	val onCancelListenr: (() -> Unit)? =null
)

fun makeDefualtDialogCOnfig() = DialogConfig(
	icon = R.drawble.ic_dialog,
	title = R.string.dialog_title,
	onCancelListener = {it.cancel() }
)

이는 실무에서 보기어 어려운 코드로, 대화상자를 정의하려는 경우, 함수를 사용해서
만들고 정의 요소를 옵션 아규먼트로 전달하는 것이 좋다.

결론적으로 코틀린에서는 빌더 패턴을 거의 사용하지 않습니다.

빌더패턴은 다음과 같은 경우에만 사용한다.

  • 빌더패턴을 사용하는 다른언어로 작성된 라이브러리를 그대로 올믹ㄹ때
  • 디폴트 아규먼트와 DSL을 지우너하지 않는 다른언어에서 쉽게 사용할 수 있게 API를 설계 할때

정리

점층적 생성자 대신 디폴트 아규먼트롤 활용해라.

빌더 패턴 대신, 기본생성자를 사용하거나 DSL을 활용하자.

profile
쉽게 가르칠수 있도록 노력하자

0개의 댓글