简体   繁体   中英

Pimp my library pattern applied to class hierarchy

I have a following class hierarchy.

sealed abstract class A
case class B extends A
case class C extends A

Now, I want to add a foo method to classes A , B , and C . But I don't want to change the classes in any way. So I apply the pimp my library pattern as follows.

abstract class RichA(a: A) {
    def foo
}
class RichB(b: B) extends RichA(b){
    def foo = { println("B"); //do something with b}
}
class RichC(c: C) extends RichA(c) {
    def foo = { println("C"); //do something with c}
}
/////////////
implicit def A2RichA(a: A) = {
    a match {
    case a: B => new RichB(a)
    case a: C => new RichC(a)
    }
}
implicit def B2RichB(b: B) = new RichB(b)
implicit def C2RichC(c: C) = new RichC(c)
/////////////
def test() = {
    def printA(a: A) = {
        a.foo
    }
    val obj = new C
    printA(obj)
}
test() //This prints "C"

This is working but the implementation of A2RichA implicit function looks bit ugly to me as it involves case statement for each of the subclass of A. Can this be done in more elegant way? The basic requirement is, if I call foo on a object of type A , it should call appropriate method foo in B or C depending on the the dynamic type of the object.

You can make your A2RichA method slightly more elegant (or inscrutable, depending on your perspective) by allowing the compiler to supply the correct conversion:

implicit def A2RichA(a: A): RichA = 
   a match {
      case b: B => b // the compiler turns this into B2RichB(b)
      case c: C => c // the compiler turns this into C2RichC(c)
   }

implicit def B2RichB(b: B): RichA = new RichB(b)

implicit def C2RichC(c: C): RichA = new RichC(c)

However, I don't think there's a simpler way to get around the fundamental problem: you want to provide a conversion based on the dynamic type of the argument. Implicit search occurs at compile time and therefore can only supply a conversion based on the static type.

You could reflectively search for a conversion at runtime, but this would be neither simple nor elegant (and certainly inadvisable for such a small hierarchy).

Since your hierarchy is sealed, the compiler will warn you if you forget to supply a conversion when you add a new class to the hierarchy.

First approach: -- insufficient according to OP --

This will do, assuming that a single implementation of foo suits all classes in your hierarchy:

sealed abstract class A
case class B() extends A
case class C() extends A

class RichA(a: A) {
  def foo() { println(this.a.getClass.getSimpleName) }
}

implicit def a2RichA(a: A): RichA = new RichA(a)

(new C).foo() /* anon$1$C */


Second approach: -- lacks dynamic dispatch --

My second approach has been inspired by the internals of the Scala collection library . However, it lacks dynamic dispatch, so it still not solve your problem.

sealed abstract class A
case class B() extends A
case class C() extends A

abstract class RichA(a: A) {
  def foo(): Unit
}

class RichB(b: B) extends RichA(b) {
  def foo() { println("Doing B-specific stuff") }
}

class RichC(c: C) extends RichA(c) {
  def foo() { println("Doing C-specific stuff") }
}

sealed trait RichBuilder[T <: A] {
  def apply(t: T): RichA
}

implicit object RichB extends RichBuilder[B] {
  def apply(b: B) = new RichB(b)
}

implicit object RichC extends RichBuilder[C] {
  def apply(c: C) = new RichC(c)
}

implicit def a2RichA[T <: A](t: T)
                            (implicit rb: RichBuilder[T])
                            : RichA = {

  rb(t)
}

(new B).foo() /* B-specific */
(new C).foo() /* C-specific */
// (new C).asInstanceOf[A].foo() /* ERROR: Can't find an implicit */


Claims:

If you want behaviour that depends on the runtime type of an objects, there are two possibilities I can see:

  1. Pattern matching
  2. Dynamic dispatch

Pattern matching, as used in your original code, seems to lead to a situation where you have one place in the code where the matching is performed. Hence, extensions of your class hierarchy entail changes to that place - which might not be possible, eg, if the hierarchy is extended by clients.

Dynamic dispatch does not suffer from this disadvantage, which is could be the reason for why this approach is used in the Scala collection (it is briefly mentioned in the article). So you could try to use double dispatch (cf. the visitor pattern ) However, this has the disadvantage that base class A must already declare a corresponding method - which somewhat defeats the goal of the Pimp My Library Pattern. Scala 2.10s new Dynamic feature might be of help here, but due to missing experience I can't comment on that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM