Now, let's add the ability to add a pizza to our order, as well as configure it:
- First, we'll create a Topping class, which will be contained within our Pizza class:
sealed class Topping(name: String): Item(name)
object Pepperoni : Topping("Pepperoni")
object Olive : Topping("Olive")
object Pineapple : Topping("Pineapple")
- Now, we'll add a Pizza class:
sealed class Pizza(name: String) : Item(name) {
val toppings: MutableList<Topping> = mutableListOf()
}
This Pizza class extends Item and has a field, MutableList<Topping>, for storing our pizza toppings.
- Next, we're going to create some predefined types of pizza that our users can order:
class BuildYourOwn(init: Pizza.() -> Unit = {}) :
Pizza("Build Your Own Pizza") {
init {
init.invoke(this)
}
}
class PepperoniPizza(init: Pizza.() -> Unit = {}) :
Pizza("Pepperoni Pizza") {
init {
toppings.add(Pepperoni)
init.invoke(this)
}
}
Both classes take an init argument, which is a function with a Pizza receiver. This function is then called during the class initialization, giving the creators of the class a chance to configure the instance. The PepperoniPizza class also adds a default topping to the pizza.
If we add the unaryPlus operator for the Topping class, we can simplify how we add a topping within the context of configuring our Pizza instance:
operator fun Topping.unaryPlus() = toppings.add(this)
class PepperoniPizza(init: Pizza.() -> Unit = {}) :
Pizza("Pepperoni Pizza") {
init {
+Pepperoni
init.invoke(this)
}
}
Now, we're ready to add a Pizza to our order. To start, we'll create a new method on the Order class:
fun pizza(init: Pizza.() -> Unit) {
val pizza = BuildYourOwn()
pizza.init()
items[pizza] = 1
}
This method takes a function type with the Pizza receiver so that the Pizza can be configured. It also creates a default BuildYourOwn instance and adds the Pizza to the items map. With that in place, we can now add a pizza to our order:
val order = order {
soda(Coke)
+Sprite
Dr_Pepper quantity 2
pizza {
+Pineapple
}
}
What if we want to add a predefined Pizza and then configure it ourselves? To do that, we can add the unaryPlus operator for Pizza within our Order class:
operator fun Pizza.unaryPlus() {
items.put(this, items.getOrDefault(this, 0) + 1)
}
Now, we can use the + operator to add the pizza and then still pass in our configuration block to update the toppings:
val order = order {
soda(Coke)
+Sprite
Dr_Pepper quantity 2
pizza {
+Pineapple
}
+HawaiianPizza {
+Pepperoni
}
}
We now have several ways of adding both Soda and Pizza instances to our order. Now, let's take a look at adding @DslMarker to ensure that our configuration blocks default functionality that is specific to the most local scope.