I have a trait that looks like this:
trait Ingredient[T] {
def foo(t: T): Unit = {
// Some complex logic
}
}
And types for which I want to have methods:
class Cheese
class Pepperoni
class Oregano
How can I make another trait that has methods:
def foo(t: Cheese)
def foo(t: Pepperoni)
def foo(t: Oregano)
without duplicating the code? The following will not work as it has illegal inheritance from the same trait multiple times:
trait Pizza extends Ingredient[Cheese] with Ingredient[Pepperoni] with Ingredient[Oregano] {}
The solution is to create objects that extend the Ingredient
trait in Pizza
trait. This way we have:
trait Pizza {
object CheeseIngredient extends Ingredient[Cheese]
object PepperoniIngredient extends Ingredient[Pepperoni]
object OreganoIngredient extends Ingredient[Oregano]
}
Then we can call our methods on inherited objects:
object LargePizza extends Pizza {
def bar(cheese: Cheese, pepperoni: Pepperoni, oregano: Oregano): Unit = {
CheeseIngredient.foo(cheese)
PepperoniIngredient.foo(pepperoni)
OreganoIngredient.foo(oregano)
}
}
Alternatively if we have only a few methods in Ingredient
trait we can create overloads and encapsulate our supporting objects:
trait Pizza {
private object CheeseIngredient extends Ingredient[Cheese]
private object PepperoniIngredient extends Ingredient[Pepperoni]
private object OreganoIngredient extends Ingredient[Oregano]
def foo(cheese: Cheese): Unit = CheeseIngredient.foo(cheese)
def foo(pepperoni: Pepperoni): Unit = PepperoniIngredient.foo(pepperoni)
def foo(oregano: Oregano): Unit = OreganoIngredient.foo(oregano)
}
object LargePizza extends Pizza {
def bar(cheese: Cheese, pepperoni: Pepperoni, oregano: Oregano): Unit = {
foo(cheese)
foo(pepperoni)
foo(oregano)
}
}
The same could be achieved by just using imports in LargePizza
. Adding overloads is better though as the client does not need to write additional imports and does not have the supporting objects in scope. The other benefit of using overloads is that the methods can be overridden in child classes.