Use double dispatch. Each original overload first sends a dynamically dispatched message to its argument. The argument's selected body then calls an overloaded receiver method with this, whose static type inside that body is the desired argument software class.
class X {
  def sendTo(m: M): String = m.receive(this)
}
class Y extends X {
  override def sendTo(m: M): String = m.receive(this)
}
class Z extends Y {
  override def sendTo(m: M): String = m.receive(this)
}

class M {
  def f(x: X): String = x.sendTo(this)
  def f(x: Y): String = x.sendTo(this)

  def receive(x: X): String = "f with X at M"
  def receive(x: Y): String = "f with Y at M"
  def receive(x: Z): String = "f with Z at M"
}

class N extends M {
  override def f(x: Y): String = x.sendTo(this)
  def f(x: Z): String = x.sendTo(this)

  override def receive(x: X): String = "f with X at N"
  override def receive(x: Y): String = "f with Y at N"
  override def receive(x: Z): String = "f with Z at N"
}
All three receive overloads must be declared in M, because sendTo receives a parameter with static type M. Declaring the Z version only in N would repeat the original problem.
Whatever original f signature is selected, it invokes sendTo on the actual argument. For a Z object, dynamic dispatch enters Z.sendTo; inside that body, this has static type Z, so method overloading selects receive(Z). The final call dynamically dispatches on the actual m, giving N.receive(Z) when m refers to N. Hence
f with Z at N
f with Z at N
f with Z at N
The result depends on both runtime types:
For the stated X, Y, Z hierarchy, the same construction reports each argument software class correctly for either receiver software class. Further subclasses would need their own dispatch protocol to distinguish additional runtime types.

Articles by others on the same topic (0)

There are currently no matching articles.