Scala Шаблон адаптера - автоматически разрешает "вводить утку" для классов с теми же методами - PullRequest
2 голосов
/ 28 января 2020

Скажем, класс A используется в некотором коде, и я хочу использовать вместо него класс B, который имеет точно такие же методы, что и класс A - без B extension A. Что было бы самым простым способом go по этому поводу? Другими словами, я ищу простой готовый к использованию и generi c реализацию 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

Однако я могу использовать жестко закодированные типы для конкретный c сценарии использования:

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 макросах, чтобы реализовать это, или если это возможно.

Ответы [ 2 ]

2 голосов
/ 29 января 2020

Вы только что пропустили несколько вещей, пытаясь использовать метод 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"
1 голос
/ 28 января 2020

Вот предлагаемое решение класса типов .

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"

Теперь это решение требует совсем немного рефакторинг, но у него есть то преимущество, что он работает, он расширяемый, он более гибкий и более безопасный по сравнению с предлагаемым решением для адаптации.

...