복잡한 객체를 생성하는 방법을 정의하는 클래스와 표현하는 방법을 정의하는 클래스를 별도로 분리.
서로 다른 표현이라도 이를 생성할 수 있는 동일한 절차를 제공
/*
빌더에 사용될 복잡한 Data Class 정의합니다.
*/
data class Tour(
val name: String,
val startDate: LocalDate,
val nights: Int,
val days: Int,
val whereToStay: String,
val detailPlans: List<DetailPlan>
) {
override fun toString(): String {
val sb = StringBuffer()
sb.appendLine("==== $name ====")
.appendLine("date: $startDate")
.appendLine("$nights 박 $days 일")
.appendLine("stay: $whereToStay")
.appendLine("plan")
.appendLine(detailPlans.makeToString())
return sb.toString()
}
private fun List<DetailPlan>.makeToString(): String {
val sb = StringBuilder()
this.forEach {
sb.append(it)
.append("\n")
}
if (this.isEmpty()) sb.append("There are no detailed plans.")
return sb.toString()
}
}
data class DetailPlan(
val no: Int,
val plan: String
) {
override fun toString(): String {
return "$no - $plan"
}
}
Tour 객체를 생성하기 위해서는 총 6가지의 파라미터를 넣어주어야하며,
nights, days 객체의 상관관계가 포함되어야 합니다.
fun main() {
// 예시
val tour = Tour(
name = "tour",
startDate = LocalDate.now(),
nights = -123,
days = 4,
whereToStay = "",
detailPlans = listOf()
)
}
위 예시 처럼 -123박 4일 이라는 불완전한 객체가 생성 되어 버렸습니다.
Builder Pattern 을 사용하면 이러한 경우를 방지하는 코드를 만들 수 있고, 객체간의 상관 관계를 정의 하여 객체를 완전하게 생성할 수 있게 됩니다.
interface TourPlanBuilder {
fun nightsAndDays(nights: Int, days: Int): TourPlanBuilder
fun title(title: String): TourPlanBuilder
fun startDate(startDate: LocalDate): TourPlanBuilder
fun whereToStay(whereToStay: String): TourPlanBuilder
fun addPlan(no: Int, plan: String): TourPlanBuilder
fun removePlan(plan: DetailPlan): TourPlanBuilder
fun build(): Tour
}
Tour
객체 생성에 필요한 필드를 설정할 수 있도록 메소드를 만듭니다.
직접 적인 구현은 Builder Interface 를 상속받은 Class 에서 정의해주면 됩니다. 여기서는 객체 생성에 필요한 함수만 만들어 주면 됩니다.
class DefaultTourBuilder : TourPlanBuilder {
private var name: String = "Default Tour"
private var startDate: LocalDate = LocalDate.now()
private var nights: Int = 0
private var days: Int = 1
private var whereToStay: String = "Hotel"
private val detailPlans: MutableList<DetailPlan> = mutableListOf()
override fun nightsAndDays(nights: Int, days: Int): TourPlanBuilder = apply {
this.nights = nights
this.days = days
}
override fun title(title: String): TourPlanBuilder = apply {
this.name = title
}
override fun startDate(startDate: LocalDate): TourPlanBuilder = apply {
this.startDate = startDate
}
override fun whereToStay(whereToStay: String): TourPlanBuilder = apply {
this.whereToStay = whereToStay
}
override fun addPlan(no: Int, plan: String): TourPlanBuilder = apply {
this.detailPlans.add(DetailPlan(no, plan))
}
override fun removePlan(plan: DetailPlan): TourPlanBuilder = apply {
if (this.detailPlans.contains(plan)) {
this.detailPlans.remove(plan)
}
}
override fun build(): Tour {
println("Create Default Tour !!")
return Tour(name, startDate, nights, days, whereToStay, detailPlans)
}
}
Kotlin 의 최대 장점인 확장 함수를 통해 Concreate Builder Class 만들었습니다.
apply
를 활용하여 builder 객체를 무조건 적으로 반환할 수 있도록 구현 할 수 있습니다.
마지막에 build()
함수를 통해 우리가 원하는 Tour
객체를 만들 수 있습니다.
Tour
객체에 필요한 파라미터 변수는 Concreate class 에서만 접근 할 수 있도록 private
로 선언해야합니다. 그래서 interface 로 추가해준 함수로 값을 접근할 수 있도록 해야 합니다.
class BuilderClientSimple {
init {
val defaultTourBuilder = DefaultTourBuilder()
val tour = defaultTourBuilder
.title("JangHee Tour")
.nightsAndDays(2, 3)
.startDate(LocalDate.of(2023, 3, 13))
.whereToStay("근사한 호텔")
.addPlan(1, "비행기 타기")
.addPlan(2, "바다 보기")
.addPlan(3, "맛있는 저녁 먹기")
.build()
println(tour)
val simpleTour = DefaultTourBuilder()
.title("Simple Tour")
.startDate(LocalDate.of(2023, 5, 5))
.build()
println(simpleTour)
}
}
private fun main() {
BuilderClientSimple()
}
2가지의 예시 투어를 만들었습니다.
1번은 상세계획이 있는 Tour 이고
2번은 간단한 Tour 입니다.
builder에 함수를 계속 추가하여 객체를 생성합니다.
1번 자세한 Tour
2번 간단한 Tour
만들기 복잡한 객체를 순차적으로 만들 수 있다.
복잡한 객체를 만드는 과정을 숨길 수 있다.
동일한 프로세스를 통해 각기 다르게 구성된 객체를 만들 수 있다.
불완전한 객체를 사용하지 못하도록 방지 할 수 있다.
원하는 객체를 만들기 전에 빌더 부터 만들어야 한다.
구조가 복잡해 진다.