Build nested models through nested builders
Nested builders let the syntax mirror a domain hierarchy. Each inner block should produce one validated value, which is then attached to its parent.
data class Section(val name: String, val lessons: List<String>)
class SectionBuilder {
private val lessons = mutableListOf<String>()
fun lesson(title: String) { require(title.isNotBlank()); lessons.add(title) }
fun build(name: String): Section {
require(name.isNotBlank() && lessons.isNotEmpty())
return Section(name, lessons.toList())
}
}
class CourseBuilder {
private val sections = mutableListOf<Section>()
fun section(name: String, block: SectionBuilder.() -> Unit) {
sections.add(SectionBuilder().apply(block).build(name))
}
fun build(): List<Section> = sections.toList()
}
The section is appended only after validation succeeds. Do not append a half-built mutable section first, since later failures can leave partially configured state.
Exercise
Construct two sections, reject an empty section, and verify the parent does not retain the failed section. Add a rule requiring unique section names.
Check: decide whether each invariant belongs to the child or to the parent that sees all siblings.