简体   繁体   English

Scala 适配器模式 - 自动允许对具有相同方法的类进行“鸭子输入”

[英]Scala Adapter pattern - autommagically allow "duck typing" for classes with same methods

Let's say class A is being used in some code, and that I want to use instead class B , which has exactly the same methods as class A - without having B extend A .假设类A正在某些代码中使用,而我想改用类B ,它具有与类A完全相同的方法 - 没有B扩展A What would be the easiest way to go about this?解决这个问题的最简单方法是什么? In other words, I'm looking for simple ready-to-use and generic implementation of adaptBtoA (it should work for any two classes that have the same structure/methods).换句话说,我正在寻找简单的即用型和通用adaptBtoA实现(它应该适用于具有相同结构/方法的任何两个类)。

class A {
  def foo(x: String) = "A_" + x
}

class B {
  def foo(x: String) = "B_" + x
}

def bar(a: A) = {
  // ...
}

bar(adaptBtoA(new B()))

If you're familiar with duck typing interfaces of Go, that's sort of the semantics I'm aiming for.如果您熟悉 Go 的鸭子类型接口,这就是我所针对的语义。


EDIT编辑

I think a generalized solution might be impossible due to type erasure, although I'm not sure.我认为由于类型擦除,通用解决方案可能是不可能的,尽管我不确定。 Here's my attempt using the mockito library:这是我使用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

unfortunately this does not work as I get the following compiler error:不幸的是,这不起作用,因为我收到以下编译器错误:

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

I can, however, use hardcoded types for specific use-cases:但是,我可以针对特定用例使用硬编码类型:

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"

If getting class type information from template parameter at runtime is impossible, perhaps I can use macros to generate all of the adapt functions I need at compile time?如果在运行时从模板参数获取类类型信息是不可能的,也许我可以使用宏来生成我在编译时需要的所有adapt函数? Then the code will look something like:然后代码将如下所示:

genAdapt[B, A]()
genAdapt[D, C]()
// etc...

But I don't know enough about scala macros yet to implement this, or if this is possible.但我对 Scala 宏的了解还不够多,无法实现这一点,或者这是否可能。

You just missed a few things in your attempt at the adapt method.在尝试使用adapt方法时,您只是错过了一些事情。 The compiler says: it needs T to extends AnyRef , and a ClassTag[T] .编译器说:它需要T来扩展AnyRef和一个ClassTag[T] You'd also need a ClassTag[F] since you'd invoke the method on F , not 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"

Here is the proposed typeclass solution.这是建议的类型类解决方案。

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")
}

Which you can use like this:你可以这样使用:

bar(new A()) 
// res: String = "A_test"

bar(new B()) 
// res: String = "B_test"

Now, this solution requires quite a bit of refactoring, but it has the advantage that it works, it is extensible and it is more flexible and more typesafe than the proposed adapt solution.现在,这个解决方案需要相当多的重构,但它的优点是它可以工作,它是可扩展的,并且比提议的适应解决方案更灵活和更安全。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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