3

我有以下类层次结构。

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

现在,我想向fooclasses ABC. 但我不想以任何方式改变课程。所以我如下应用皮条客我的图书馆模式。

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 语句。这可以用更优雅的方式完成吗?基本要求是,如果我调用footype 的对象A,它应该调用适当的方法fooB或者C取决于对象的动态类型。

4

2 回答 2

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)

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

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

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

于 2012-07-18T18:55:58.733 回答
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 集合库内部结构的启发。但是,它缺乏动态调度,所以它仍然不能解决你的问题。

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. 动态调度

在您的原始代码中使用的模式匹配似乎会导致您在执行匹配的代码中有一个位置。因此,你的类层次结构的扩展需要改变那个地方——这可能是不可能的,例如,如果层次结构是由客户扩展的。

动态调度没有这个缺点,这可能是为什么在 Scala 集合中使用这种方法的原因(在文章中简要提到过)。所以你可以尝试使用双重调度(参见访问者模式)但是,这有一个缺点,即基类A必须已经声明了一个相应的方法——这在某种程度上违背了皮条客我的图书馆模式的目标。Scala 2.10 的新动态特性在这里可能会有所帮助,但由于缺少经验,我无法对此发表评论。

于 2012-07-18T08:46:30.537 回答