Design patterns are an essential part of software development. Among these, the Builder Pattern stands out for its ability to create complex objects step-by-step. While Kotlin provides powerful features like apply
, with
, and named arguments that often eliminate the need for classic patterns, implementing the Builder Pattern from scratch is still an excellent exercise to understand object creation and design principles.
In this blog, we'll build our own version of the Builder Pattern in Kotlin. Let's dive in!
The Builder Pattern is a creational design pattern used to construct complex objects step-by-step. Instead of creating the object in a single constructor call, the Builder Pattern allows us to build it incrementally, setting various properties along the way.
Step-by-step construction: Build the object one piece at a time.
Immutability: The final object is often immutable once built.
Fluent Interface: Methods return the builder itself, allowing for a chainable API.
The Builder Pattern is useful when:
You need to construct an object with many optional or configurable parameters.
The object initialization logic is complex.
You want to make your code more readable and flexible.
Let's take the example of building a RajasthanTrip
class with properties like destination
, duration
, activities
, and luxuryAccommodation
.
First, define the RajasthanTrip
class with properties:
class RajasthanTrip private constructor(
val destination: String,
val duration: Int, // in days
val activities: List<String>,
val luxuryAccommodation: Boolean
) {
override fun toString(): String {
return "RajasthanTrip(destination='$destination', duration=$duration days, activities=$activities, luxuryAccommodation=$luxuryAccommodation)"
}
}
This is where we define mutable properties and methods to configure the RajasthanTrip
object step-by-step.
companion object {
// A function to easily create a RajasthanTrip using a DSL
fun create(
destination: String = "Jaipur",
duration: Int = 3,
luxuryAccommodation: Boolean = false,
buildActivities: MutableList<String>.() -> Unit = {}
): RajasthanTrip {
val activities = mutableListOf<String>().apply(buildActivities)
return RajasthanTrip(destination, duration, activities, luxuryAccommodation)
}
}
Now, let's use the builder to construct a RajasthanTrip
object:
fun main() {
// Using the DSL-style builder for easy construction
val trip = RajasthanTrip.create(
destination = "Udaipur",
duration = 5,
luxuryAccommodation = true
) {
add("Visit City Palace")
add("Lake Pichola Boat Ride")
}
println(trip)
}
RajasthanTrip(destination='Udaipur', duration=5 days, activities=[Visit City Palace, Lake Pichola Boat Ride], luxuryAccommodation=true)
apply
and Builder PatternKotlin's apply
and named arguments often eliminate the need for a traditional Builder Pattern. For example, the same RajasthanTrip
object could be constructed using:
val trip = RajasthanTrip(
destination = "Udaipur",
duration = 5,
activities = listOf("Visit City Palace", "Lake Pichola Boat Ride"),
luxuryAccommodation = true
)
However, the Builder Pattern becomes invaluable when dealing with more complex object creation logic or when maintaining backward compatibility in large codebases.
Customization: You can add specific validation rules during object creation.
Readability: A fluent, chainable API improves code clarity.
Code Maintenance: Decouples the construction logic from the main class.
You can enhance the builder with validation logic. For instance, ensuring a RajasthanTrip
has at least one activity:
fun build(): RajasthanTrip {
if (activities.isEmpty()) {
throw IllegalArgumentException("RajasthanTrip must have at least one activity")
}
return RajasthanTrip(destination, duration, activities, luxuryAccommodation)
}
The Builder Pattern is a classic design pattern that provides a structured and readable way to construct objects. While Kotlin's features often simplify the need for traditional patterns, implementing the Builder Pattern from scratch gives you insights into object creation and design principles.
Try creating your own builder for another class and see how it transforms your code! Let me know your thoughts or share your implementation in the comments below.
Akshay Nandwana
Founder AndroidEngineers
You can connect with me on:
Join our upcoming classes
https://www.androidengineers.in/courses