Kotlin DSL для коллекций - PullRequest
       5

Kotlin DSL для коллекций

0 голосов
/ 19 октября 2018

Это простая структура данных House, которая содержит (обнуляемый) список Person:

data class Person(val name: String,
                  val militaryYear: Int? = null,
                  val firstPregnantYear: Int? = null)
data class House(val persons: List<Person>?)

Person может быть мужской или женский.Male имеет значение militaryYear, Female имеет значение firstPregnantYear.

Я хочу написать DSL для построения объекта House, например:

val house = house {
  person("John") {
    militaryYear = 2003
  }
  person("Anne") {
    firstPregnantYear = 2010
  }
}

Это то, что ясделали:

data class Person(val name: String,
                  val militaryYear: Int? = null,
                  val firstPregnantYear: Int? = null) {

  class Builder(private val name: String) {
    var militaryYear : Int? = null
    var firstPregnantYear : Int? = null
    fun build(): Person = Person(name, militaryYear, firstPregnantYear)
  }
}
fun person(name: String , block: Person.Builder.() -> Unit) = Person.Builder(name).apply(block).build()



data class House(val persons: List<Person>?) {
  class Builder {
    var persons = mutableListOf<Person>()

    fun person(name: String , block: Person.Builder.() -> Unit) {
      persons.add(Person.Builder(name).apply(block).build())
    }

    fun build(): House = House(persons = persons.takeIf { it.isNotEmpty() })
  }
}

Кажется, работает, но не запрещает оба militaryYear & firstPregnantYear, например:

val house = house {
  person("John") {
    militaryYear = 2003
  }
  person("Anne") {
    firstPregnantYear = 2010
  }
  person("Alien") {
    militaryYear = 2005
    firstPregnantYear = 2009
  }
}
println(house)

Результат:

House(persons=[
Person(name=John, militaryYear=2003, firstPregnantYear=null)
, Person(name=Anne, militaryYear=null, firstPregnantYear=2010)
, Person(name=Alien, militaryYear=2005, firstPregnantYear=2009)
])

В Alien есть militaryYear & firstPregnantYear, что неверно.

Итак, я добавляю еще MaleBuilder и FemaleBuilder:

  class MaleBuilder(private val name: String) {
    var militaryYear = 0
    fun build(): Person = Person(name, militaryYear, null)
  }

  class FemaleBuilder(private val name: String) {
    var firstPregnantYear = 0
    fun build() : Person = Person(name , null , firstPregnantYear)
  }

fun male(name: String , block: Person.MaleBuilder.() -> Unit) = Person.MaleBuilder(name).apply(block).build()
fun female(name: String , block: Person.FemaleBuilder.() -> Unit) = Person.FemaleBuilder(name).apply(block).build()

И DSL становится:

val house = house {
  male("John") {
    militaryYear = 2003
  }
  female("Anne") {
    firstPregnantYear = 2010
  }
}
println(house)

ОК, вот проблема, male("John") и female("Anne") не вставляются в persons.Он выводит:

House(persons=null)

Я думаю, что проблема связана с функцией человека () Хауса:

fun person(name: String , block: Person.Builder.() -> Unit) {
  persons.add(Person.Builder(name).apply(block).build())
}

Определенный получатель Person.Builder, но это MaleBuilder и FemaleBuilder в коде.

Как решить эту проблему?

Полный код: https://pastebin.com/1Q6D8rzx

------- обновлено -------

Это можно решить, введя функции House.Builder.male() и House.Builder.female():

fun male(name: String , block: Person.MaleBuilder.() -> Unit) {
  persons.add(Person.MaleBuilder(name).apply(block).build())
}

fun female(name: String , block: Person.FemaleBuilder.() -> Unit) {
  persons.add(Person.FemaleBuilder(name).apply(block).build())
}

Но я думаю, что это не очень хорошее решение.В примере с реальным миром может быть много свойств и много ограничений.Есть ли способ использовать одну функцию для ее получения?

...