Pimp我的库模式应用于类层次结构

时间:2012-07-18 08:14:47

标签: scala polymorphism implicit

我有以下类层次结构。

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

现在,我想在课程fooAB中添加C方法。但我不想以任何方式改变类。所以我将pimp我的库模式应用如下。

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"

这是有效的,但A2RichA隐式函数的实现看起来有点难看,因为它涉及A的每个子类的case语句。这可以更优雅的方式完成吗?基本要求是,如果我在foo类型的对象上调用A,则应在fooB中调用适当的方法C,具体取决于动态类型的对象。

2 个答案:

答案 0 :(得分:1)

通过允许编译器提供正确的转换,您可以使A2RichA方法稍微优雅(或不可思议,具体取决于您的观点):

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)

但是,我认为有一种更简单的方法可以解决基本问题:您希望提供基于参数的动态类型的转换。隐式搜索在编译时发生,因此只能提供基于 static 类型的转换。

你可以反思地在运行时搜索转换,但这既不简单也不优雅(对于如此小的层次结构当然不建议)。

由于您的层次结构是密封的,如果您在向层次结构添加新类时忘记提供转换,编译器警告您。

答案 1 :(得分:0)

第一种方法: - 根据OP不足 -

这样做,假设foo的单个实现适合您的层次结构中的所有类:

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 */


第二种方法: - 缺乏动态调度 -

我的第二种方法受到了Scala collection library内部的启发。但是,它缺乏动态调度,所以它仍然无法解决您的问题。

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 */


声明:

如果您希望行为取决于对象的运行时类型,我可以看到两种可能性:

  1. 模式匹配
  2. 动态调度
  3. 原始代码中使用的模式匹配似乎会导致代码中有一个位置执行匹配。因此,类层次结构的扩展需要对该位置进行更改 - 这可能是不可能的,例如,如果层次结构是由客户端扩展的。

    动态调度没有这个缺点,这可能是为什么在Scala集合中使用这种方法的原因(在文章中简要提到)。所以你可以尝试使用双重调度(参见visitor pattern)但是,这样做的缺点是基类A必须已经声明了一个相应的方法 - 这有点挫败了Pimp My Library Pattern的目标。 Scala 2.10的新Dynamic功能可能对此有所帮助,但由于缺少经验,我无法评论。