객체를 정의하고 생성하는 가장 기본적인 방법은 기본 생성자(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을 활용하자.