[디자인 패턴] 빌더 패턴

Benji Android·2023년 3월 19일
0

디자인 패턴

목록 보기
4/7
post-thumbnail

정의

복잡한 객체를 생성하는 방법을 정의하는 클래스와 표현하는 방법을 정의하는 클래스를 별도로 분리.
서로 다른 표현이라도 이를 생성할 수 있는 동일한 절차를 제공

  • Android 개발을 진행하면 Builder Pattern을 많이 찾아 볼 수 있습니다. ex) Dialog, Retrofit, Okhttp, Glidle 등

구현할 클래스 목록

  • BuilderClientSimple: 빌더 항목을 사용합니다.
  • TourPlanBuilder: 빌더 생성에 필요한 메소드를 제공
  • Tour, DetailPlan: 빌더를 만들기 위한 Data Class(Product)
  • DefaultTourBuilder: 인터페이스를 구현하여 Tour 객체를 만듬

Product 만들기

/*
  빌더에 사용될 복잡한 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 을 사용하면 이러한 경우를 방지하는 코드를 만들 수 있고, 객체간의 상관 관계를 정의 하여 객체를 완전하게 생성할 수 있게 됩니다.


Builder Interface 만들기

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 에서 정의해주면 됩니다. 여기서는 객체 생성에 필요한 함수만 만들어 주면 됩니다.


Concreate Builder 만들기

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 로 추가해준 함수로 값을 접근할 수 있도록 해야 합니다.


Client 만들기

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


장점

만들기 복잡한 객체를 순차적으로 만들 수 있다.
복잡한 객체를 만드는 과정을 숨길 수 있다.
동일한 프로세스를 통해 각기 다르게 구성된 객체를 만들 수 있다.
불완전한 객체를 사용하지 못하도록 방지 할 수 있다.

단점

원하는 객체를 만들기 전에 빌더 부터 만들어야 한다.
구조가 복잡해 진다.


참고

profile
Android 주니어 개발자

0개의 댓글