[design-patterns] Kotlin에서 빌더 패턴을 구현하는 방법은 무엇입니까?

안녕하세요 저는 Kotlin 세계의 초보자입니다. 나는 지금까지 본 것을 좋아하고 응용 프로그램에서 사용하는 일부 라이브러리를 Java에서 Kotlin으로 변환하려고 생각하기 시작했습니다.

이 라이브러리는 setter, getter 및 Builder 클래스가있는 Pojo로 가득합니다. 이제 Kotlin에서 빌더를 구현하는 가장 좋은 방법은 무엇인지 알아 보았지만 성공하지 못했습니다.

두 번째 업데이트 : 문제는 Kotlin에서 일부 매개 변수를 사용하여 간단한 pojo에 대한 빌더 디자인 패턴을 작성하는 방법입니다. 아래 코드는 Java 코드를 작성한 다음 eclipse-kotlin-plugin을 사용하여 Kotlin으로 변환하여 시도한 것입니다.

class Car private constructor(builder:Car.Builder) {
    var model:String? = null
    var year:Int = 0
    init {
        this.model = builder.model
        this.year = builder.year
    }
    companion object Builder {
        var model:String? = null
        private set

        var year:Int = 0
        private set

        fun model(model:String):Builder {
            this.model = model
            return this
        }
        fun year(year:Int):Builder {
            this.year = year
            return this
        }
        fun build():Car {
            val car = Car(this)
            return car
        }
    }
}



답변

무엇보다도 기본적으로 명명 된 인수가 있기 때문에 대부분의 경우 Kotlin에서 빌더를 사용할 필요가 없습니다. 이것은 당신이 쓸 수 있습니다

class Car(val model: String? = null, val year: Int = 0)

다음과 같이 사용하십시오.

val car = Car(model = "X")

빌더를 절대적으로 사용하려면 다음과 같이하십시오.

S는 싱글 톤 companion object이기 때문에 Builder를 만드는 것은 의미가 없습니다 object. 대신 중첩 클래스로 선언하십시오 (Kotlin에서는 기본적으로 정적입니다).

객체를 규칙적으로 인스턴스화 할 수 있도록 속성을 생성자로 이동하고 (생성하지 않아야하는 경우 생성자를 비공개로 설정) 빌더를 사용하여 기본 생성자로 위임하는 보조 생성자를 사용하십시오. 코드는 다음과 같습니다.

class Car( //add private constructor if necessary
        val model: String?,
        val year: Int
) {

    private constructor(builder: Builder) : this(builder.model, builder.year)

    class Builder {
        var model: String? = null
            private set

        var year: Int = 0
            private set

        fun model(model: String) = apply { this.model = model }

        fun year(year: Int) = apply { this.year = year }

        fun build() = Car(this)
    }
}

용법: val car = Car.Builder().model("X").build()

빌더 DSL 을 사용하여이 코드를 추가로 단축 할 수 있습니다 .

class Car (
        val model: String?,
        val year: Int
) {

    private constructor(builder: Builder) : this(builder.model, builder.year)

    companion object {
        inline fun build(block: Builder.() -> Unit) = Builder().apply(block).build()
    }

    class Builder {
        var model: String? = null
        var year: Int = 0

        fun build() = Car(this)
    }
}

용법: val car = Car.build { model = "X" }

일부 값이 필요하고 기본값이없는 경우 빌더의 생성자 및 build방금 정의한 메소드에 값을 입력해야합니다 .

class Car (
        val model: String?,
        val year: Int,
        val required: String
) {

    private constructor(builder: Builder) : this(builder.model, builder.year, builder.required)

    companion object {
        inline fun build(required: String, block: Builder.() -> Unit) = Builder(required).apply(block).build()
    }

    class Builder(
            val required: String
    ) {
        var model: String? = null
        var year: Int = 0

        fun build() = Car(this)
    }
}

용법: val car = Car.build(required = "requiredValue") { model = "X" }


답변

한 가지 방법은 다음과 같은 작업을 수행하는 것입니다.

class Car(
  val model: String?,
  val color: String?,
  val type: String?) {

    data class Builder(
      var model: String? = null,
      var color: String? = null,
      var type: String? = null) {

        fun model(model: String) = apply { this.model = model }
        fun color(color: String) = apply { this.color = color }
        fun type(type: String) = apply { this.type = type }
        fun build() = Car(model, color, type)
    }
}

사용 샘플 :

val car = Car.Builder()
  .model("Ford Focus")
  .color("Black")
  .type("Type")
  .build()


답변

JSON에서 객체를 구문 분석하기 위해 Jackson 라이브러리를 사용하고 있기 때문에 빈 생성자가 필요하며 선택적 필드를 가질 수 없습니다. 또한 모든 필드는 변경 가능해야합니다. 그런 다음 빌더 패턴과 동일한 기능을하는이 멋진 구문을 사용할 수 있습니다.

val car = Car().apply{ model = "Ford"; year = 2000 }


답변

나는 개인적으로 Kotlin에서 건축업자를 본 적이 없지만 아마도 나일 것입니다.

init블록 에서 필요한 모든 유효성 검사가 발생합니다 .

class Car(val model: String,
          val year: Int = 2000) {

    init {
        if(year < 1900) throw Exception("...")
    }
}

여기 당신이 정말로 원하는하지 않는 것이 생각하는 자유를했다 modelyear변경 될 수 있습니다. 또한 그 디폴트 값은 아무 의미가없는 것 같다 (특히 null대한 name)하지만 난 데모 용으로 하나 떠났다.

의견 :
명명 된 매개 변수없이 살기위한 수단으로 Java에서 사용되는 빌더 패턴. Kotlin 또는 Python과 같은 명명 된 매개 변수가있는 언어에서는 매개 변수가 긴 (선택적) 매개 변수 목록이있는 생성자를 사용하는 것이 좋습니다.


답변

추가 재미를 빌더로 선언하는 많은 예제를 보았습니다. 나는 개인적으로이 접근법을 좋아한다. 빌더를 작성하는 노력을 절약하십시오.

package android.zeroarst.lab.koltinlab

import kotlin.properties.Delegates

class Lab {
    companion object {
        @JvmStatic fun main(args: Array<String>) {

            val roy = Person {
                name = "Roy"
                age = 33
                height = 173
                single = true
                car {
                    brand = "Tesla"
                    model = "Model X"
                    year = 2017
                }
                car {
                    brand = "Tesla"
                    model = "Model S"
                    year = 2018
                }
            }

            println(roy)
        }

        class Person() {
            constructor(init: Person.() -> Unit) : this() {
                this.init()
            }

            var name: String by Delegates.notNull()
            var age: Int by Delegates.notNull()
            var height: Int by Delegates.notNull()
            var single: Boolean by Delegates.notNull()
            val cars: MutableList<Car> by lazy { arrayListOf<Car>() }

            override fun toString(): String {
                return "name=$name, age=$age, " +
                        "height=$height, " +
                        "single=${when (single) {
                            true -> "looking for a girl friend T___T"
                            false -> "Happy!!"
                        }}\nCars: $cars"
            }
        }

        class Car() {

            var brand: String by Delegates.notNull()
            var model: String by Delegates.notNull()
            var year: Int by Delegates.notNull()

            override fun toString(): String {
                return "(brand=$brand, model=$model, year=$year)"
            }
        }

        fun Person.car(init: Car.() -> Unit): Unit {
            cars.add(Car().apply(init))
        }

    }
}

아직 예외를 throw하는 대신 오류를 표시하는 것처럼 일부 필드를 DSL에서 초기화 할 수있는 방법을 아직 찾지 못했습니다. 아는 사람이 있으면 알려주세요.


답변

간단한 수업의 경우 별도의 빌더가 필요하지 않습니다. Kirill Rakhman이 설명한대로 선택적 생성자 인수를 사용할 수 있습니다.

더 복잡한 수업이있는 경우 Kotlin은 Groovy 스타일 빌더 / DSL을 작성하는 방법을 제공합니다.

유형 안전 빌더

예를 들면 다음과 같습니다.

Github 예제-빌더 / 어셈블러


답변

요즘 사람들은 Kotlin의 Type-Safe Builders를 확인해야합니다 .

위에서 언급 한 객체 생성 방식은 다음과 같습니다.

html {
    head {
        title {+"XML encoding with Kotlin"}
    }
    // ...
}

유용한 ‘실제’사용 예는 유형 안전 빌더를 사용하여 뷰와 컴포넌트조립 하는 vaadin-on-kotlin 프레임 워크 입니다.