簡體   English   中英

Scala 適配器模式 - 自動允許對具有相同方法的類進行“鴨子輸入”

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

假設類A正在某些代碼中使用,而我想改用類B ,它具有與類A完全相同的方法 - 沒有B擴展A 解決這個問題的最簡單方法是什么? 換句話說,我正在尋找簡單的即用型和通用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()))

如果您熟悉 Go 的鴨子類型接口,這就是我所針對的語義。


編輯

我認為由於類型擦除,通用解決方案可能是不可能的,盡管我不確定。 這是我使用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"

如果在運行時從模板參數獲取類類型信息是不可能的,也許我可以使用宏來生成我在編譯時需要的所有adapt函數? 然后代碼將如下所示:

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

但我對 Scala 宏的了解還不夠多,無法實現這一點,或者這是否可能。

在嘗試使用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"

這是建議的類型類解決方案。

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"

現在,這個解決方案需要相當多的重構,但它的優點是它可以工作,它是可擴展的,並且比提議的適應解決方案更靈活和更安全。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM