Replace the overloaded entry point by one forwarding method and add a virtual callback on the drink. The callbacks choose a reaction overload using the concrete drink class's statically known this type:
class Drink {
  def serveTo(b: Boy): Unit = b.react(this)
}
class Milk extends Drink {
  override def serveTo(b: Boy): Unit = b.react(this)
}
class Boy {
  def concept(d: Drink): Unit = d.serveTo(this)
  def react(d: Drink): Unit = println("Drink")
  def react(m: Milk): Unit = println("Milk")
}
class Baby extends Boy {
  override def react(d: Drink): Unit = println("Food")
  override def react(m: Milk): Unit = println("White drink")
}
The original b.concept(d) call now dynamically selects Milk.serveTo. Inside that method this has static type Milk, selecting react(Milk); virtual dispatch on b then selects Baby.react(Milk). Thus the output is White drink. This is double dispatch: changing only an overload's parameter name or the reference's runtime object would not change static overload selection.
Scala trait linearization merges parent linearizations from right to left, deleting an earlier occurrence when that same ancestor occurs in a later appended parent linearization. Each class precedes its ancestors, and an ancestor appears once.[ref] Ignoring the predefined roots gives
TypeLinearization, most specific first
KK
II, K
JJ, K
GG, I, K
FF, J, I, K
HH, J, K
DD, F, J, G, I, K
EE, H, J, K
AA, D, F, J, G, I, K
BB, E, H, D, F, J, G, I, K
CC, E, H, J, K
Each override appends its letter after super.m returns. Reading the method chain in reverse from K therefore yields the three output lines:
KIGJFDA
KIGJFDHEB
KJHEC
In particular, mixing H into C again does not create another H or move it before its subclass E. Also, J lies between F and G in D's chain; super follows the final linearization, not simply one statically named parent.

Articles by others on the same topic (0)

There are currently no matching articles.