Scala Adapter 模式 - autommagically allow "duck typing" for 类 with same methods
Scala Adapter pattern - autommagically allow "duck typing" for classes with same methods
假设某些代码中使用了 class A
,而我想使用 class B
,它与 class A
- 无需 B
扩展 A
。最简单的方法是什么?换句话说,我正在寻找 generic adaptBtoA
的简单即用型实现(它应该适用于任何具有相同 structure/methods 的两个 class)。
class A {
def foo(x: String) = "A_" + x
}
class B {
def foo(x: String) = "B_" + x
}
def bar(a: A) = {
// ...
}
bar(adaptBtoA(new B()))
如果您熟悉 Go 的 duck typing 接口,这就是我的目标语义。
编辑
我认为由于类型擦除,通用解决方案可能是不可能的,尽管我不确定。这是我使用 mockito
库的尝试:
def adapt[F, T](impl: F): T = mock[T](new Answer[Any]() {
override def answer(inv: InvocationOnMock): Any =
classOf[T]
.getDeclaredMethod(inv.getMethod.getName, inv.getMethod.getParameterTypes:_*)
.invoke(impl, inv.getArguments:_*)
})
val a: A = adapt[B, A](new B())
val res = a.foo("test") // should be "B_test" but errors in compilation
不幸的是,这不起作用,因为我收到以下编译器错误:
type arguments [T] conform to the bounds of none of the overloaded alternatives of
value mock: [T <: AnyRef](name: String)(implicit classTag: scala.reflect.ClassTag[T])T <and> [T <: AnyRef](mockSettings: org.mockito.MockSettings)(implicit classTag: scala.reflect.ClassTag[T])T <and> [T <: AnyRef](defaultAnswer: org.mockito.stubbing.Answer[_])(implicit classTag: scala.reflect.ClassTag[T])T <and> [T <: AnyRef](implicit classTag: scala.reflect.ClassTag[T])T
但是,我可以针对特定用例使用硬编码类型:
def adaptBtoA(b: B): A = mock[A](new Answer[Any]() {
override def answer(inv: InvocationOnMock): Any =
classOf[B]
.getDeclaredMethod(inv.getMethod.getName, inv.getMethod.getParameterTypes:_*)
.invoke(b, inv.getArguments:_*)
})
val a: A = adaptBtoA(new B())
val res = a.foo("test") // res == "B_test"
如果无法在运行时从模板参数中获取 class 类型信息,也许我可以使用宏在编译时生成我需要的所有 adapt
函数?然后代码将类似于:
genAdapt[B, A]()
genAdapt[D, C]()
// etc...
但我对 scala 宏的了解还不够多,无法实现它,或者这是否可能。
这是建议的 typeclass 解决方案。
trait Foo[T] {
def foo(t: T)(x: String): String
}
object Foo {
object syntax {
implicit class FooOps[T](private val t: T) extends AnyVal {
@inline
final def foo(x: String)(implicit ev: Foo[T]): String =
ev.foo(t)(x)
}
}
}
final class A {
def foo(x: String) = s"A_${x}"
}
object A {
implicit final val AFoo: Foo[A] =
new Foo[A] {
override def foo(a: A)(x: String): String =
a.foo(x)
}
}
final class B {
def foo(x: String) = s"B_${x}"
}
object B {
implicit final val BFoo: Foo[B] =
new Foo[B] {
override def foo(b: B)(x: String): String =
b.foo(x)
}
}
def bar[T : Foo](t: T): String = {
import Foo.syntax._
t.foo("test")
}
你可以这样使用:
bar(new A())
// res: String = "A_test"
bar(new B())
// res: String = "B_test"
现在,此解决方案需要进行大量重构,但它的优点是有效、可扩展,并且比建议的 adapt 解决方案更灵活、类型更安全。
您只是在尝试 adapt
方法时遗漏了一些东西。编译器说:它需要 T
来扩展 AnyRef
,以及一个 ClassTag[T]
。您还需要一个 ClassTag[F]
,因为您会在 F
上调用该方法,而不是 T
.
def adapt[F: ClassTag, T <: AnyRef : ClassTag](impl: F): T = {
mock[T](new Answer[Any]() {
override def answer(inv: InvocationOnMock): Any =
implicitly[ClassTag[F]].runtimeClass
.getDeclaredMethod(inv.getMethod.getName, inv.getMethod.getParameterTypes: _*)
.invoke(impl, inv.getArguments: _*)
})
}
adapt[B, A](new B()).foo("test") // "B_test"
adapt[A, B](new A()).foo("test") // "A_test"
假设某些代码中使用了 class A
,而我想使用 class B
,它与 class A
- 无需 B
扩展 A
。最简单的方法是什么?换句话说,我正在寻找 generic adaptBtoA
的简单即用型实现(它应该适用于任何具有相同 structure/methods 的两个 class)。
class A {
def foo(x: String) = "A_" + x
}
class B {
def foo(x: String) = "B_" + x
}
def bar(a: A) = {
// ...
}
bar(adaptBtoA(new B()))
如果您熟悉 Go 的 duck typing 接口,这就是我的目标语义。
编辑
我认为由于类型擦除,通用解决方案可能是不可能的,尽管我不确定。这是我使用 mockito
库的尝试:
def adapt[F, T](impl: F): T = mock[T](new Answer[Any]() {
override def answer(inv: InvocationOnMock): Any =
classOf[T]
.getDeclaredMethod(inv.getMethod.getName, inv.getMethod.getParameterTypes:_*)
.invoke(impl, inv.getArguments:_*)
})
val a: A = adapt[B, A](new B())
val res = a.foo("test") // should be "B_test" but errors in compilation
不幸的是,这不起作用,因为我收到以下编译器错误:
type arguments [T] conform to the bounds of none of the overloaded alternatives of
value mock: [T <: AnyRef](name: String)(implicit classTag: scala.reflect.ClassTag[T])T <and> [T <: AnyRef](mockSettings: org.mockito.MockSettings)(implicit classTag: scala.reflect.ClassTag[T])T <and> [T <: AnyRef](defaultAnswer: org.mockito.stubbing.Answer[_])(implicit classTag: scala.reflect.ClassTag[T])T <and> [T <: AnyRef](implicit classTag: scala.reflect.ClassTag[T])T
但是,我可以针对特定用例使用硬编码类型:
def adaptBtoA(b: B): A = mock[A](new Answer[Any]() {
override def answer(inv: InvocationOnMock): Any =
classOf[B]
.getDeclaredMethod(inv.getMethod.getName, inv.getMethod.getParameterTypes:_*)
.invoke(b, inv.getArguments:_*)
})
val a: A = adaptBtoA(new B())
val res = a.foo("test") // res == "B_test"
如果无法在运行时从模板参数中获取 class 类型信息,也许我可以使用宏在编译时生成我需要的所有 adapt
函数?然后代码将类似于:
genAdapt[B, A]()
genAdapt[D, C]()
// etc...
但我对 scala 宏的了解还不够多,无法实现它,或者这是否可能。
这是建议的 typeclass 解决方案。
trait Foo[T] {
def foo(t: T)(x: String): String
}
object Foo {
object syntax {
implicit class FooOps[T](private val t: T) extends AnyVal {
@inline
final def foo(x: String)(implicit ev: Foo[T]): String =
ev.foo(t)(x)
}
}
}
final class A {
def foo(x: String) = s"A_${x}"
}
object A {
implicit final val AFoo: Foo[A] =
new Foo[A] {
override def foo(a: A)(x: String): String =
a.foo(x)
}
}
final class B {
def foo(x: String) = s"B_${x}"
}
object B {
implicit final val BFoo: Foo[B] =
new Foo[B] {
override def foo(b: B)(x: String): String =
b.foo(x)
}
}
def bar[T : Foo](t: T): String = {
import Foo.syntax._
t.foo("test")
}
你可以这样使用:
bar(new A())
// res: String = "A_test"
bar(new B())
// res: String = "B_test"
现在,此解决方案需要进行大量重构,但它的优点是有效、可扩展,并且比建议的 adapt 解决方案更灵活、类型更安全。
您只是在尝试 adapt
方法时遗漏了一些东西。编译器说:它需要 T
来扩展 AnyRef
,以及一个 ClassTag[T]
。您还需要一个 ClassTag[F]
,因为您会在 F
上调用该方法,而不是 T
.
def adapt[F: ClassTag, T <: AnyRef : ClassTag](impl: F): T = {
mock[T](new Answer[Any]() {
override def answer(inv: InvocationOnMock): Any =
implicitly[ClassTag[F]].runtimeClass
.getDeclaredMethod(inv.getMethod.getName, inv.getMethod.getParameterTypes: _*)
.invoke(impl, inv.getArguments: _*)
})
}
adapt[B, A](new B()).foo("test") // "B_test"
adapt[A, B](new A()).foo("test") // "A_test"