我有以下类层次结构。
sealed abstract class A
case class B extends A
case class C extends A
现在,我想在课程foo
,A
和B
中添加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
,则应在foo
或B
中调用适当的方法C
,具体取决于动态类型的对象。
答案 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 */
的声明:强>
如果您希望行为取决于对象的运行时类型,我可以看到两种可能性:
原始代码中使用的模式匹配似乎会导致代码中有一个位置执行匹配。因此,类层次结构的扩展需要对该位置进行更改 - 这可能是不可能的,例如,如果层次结构是由客户端扩展的。
动态调度没有这个缺点,这可能是为什么在Scala集合中使用这种方法的原因(在文章中简要提到)。所以你可以尝试使用双重调度(参见visitor pattern)但是,这样做的缺点是基类A
必须已经声明了一个相应的方法 - 这有点挫败了Pimp My Library Pattern的目标。 Scala 2.10的新Dynamic功能可能对此有所帮助,但由于缺少经验,我无法评论。