如何覆盖导入的隐式值?

How to override an implicit value, that is imported?

我已经尝试过 中描述的解决方案,但没有帮助。这是一个代码示例。

具有 2 种不同实现的 TestImplicit 抽象的定义(ExecutionContextExecutor 的模拟):

trait TestImplicit {
  def f(s:String):Unit
}
object TestImplicitImpl1 extends TestImplicit {
  override def f(s: String): Unit = println(s"1: $s")
}
object TestImplicitImpl2 extends TestImplicit {
  override def f(s: String): Unit = println(s"2: $s")
}

并且在 ImplDefinition 对象中定义了一个 q 变量以通过导入隐式使用(类似于 ExecutionContext.Implicits.global):

object ImplDefinition {
  implicit val q:TestImplicit = TestImplicitImpl1
}

定义方法的客户端,隐式接受TestImplicit(scala.concurrent.Future的模拟):

trait TestImplicitClient {
  def fu(implicit ti:TestImplicit):Unit
}
object TestImplicitClient extends TestImplicitClient {

  override def fu(implicit ti: TestImplicit): Unit = {
    println("client")
    ti.f("param")
  }
}

下一步,客户端的客户端,选择应该使用 TestImplicit 的哪个实现,决定是通过 import 完成的(API 的模拟,使用 Future):

object ClientOfClient {

  import somepackage.ImplDefinition.q

  def t():Unit =
    TestImplicitClient.fu
}

现在在测试中,我想使用这个 ClientOfClient.t(),但我需要覆盖隐式,并改用 TestImplicitImpl2。背后的主要思想 - 隐式应该由 API 的客户 defined/overridable,而不是 API 本身:

import somepackage.{ClientOfClient, TestImplicit, TestImplicitImpl2}
import org.junit.Test

class ImplTest {
  // trying to hide it via import, does not help
  import somepackage.ImplDefinition.{q => _,_}

  @Test def test(): Unit ={
    //trying to hide it via downgrading to non-implicit, does not work either
    val q = somepackage.ImplDefinition.q
    implicit val ti = TestImplicitImpl2
    ClientOfClient.t()
  }
}

每次我运行测试,我在输出中得到:

client
1: param

但没想到:

client
2: param

我该如何解决?我需要一种方法来允许客户覆盖隐式并尽可能保持简单 API 。这意味着,我不想在 ClientOfClient.t() 方法中添加额外的隐式参数。

一旦你有了一个单例对象ClientOfClient,到处都有一个硬编码的常量TestImplicitImpl1,你基本上就无能为力了。但是有几种解决方法。


1。使用默认隐式参数

object ClientOfClient {
  def t()(implicit ti: TestImplicit = ImplDefinition.q): Unit =
    TestImplicitClient.fu
}

object ImplTest {
  def test(): Unit = {
    implicit val ti2 = TestImplicitImpl2
    ClientOfClient.t()
  }
}

ImplTest.test() // 2: param

2。通过可以覆盖的单独方法提供隐式

如果要使隐式可覆盖,则使 ClientOfClient 可扩展,并创建一个方法(此处为“cocti”)returns 隐式,而不是 import直接隐含。然后您可以覆盖该方法(而您不能覆盖导入)。

这里最终会产生 2: param

trait TestImplicit {
  def f(s: String): Unit
}
object TestImplicitImpl1 extends TestImplicit {
  override def f(s: String): Unit = println(s"1: $s")
}
object TestImplicitImpl2 extends TestImplicit {
  override def f(s: String): Unit = println(s"2: $s")
}

object ImplDefinition {
  implicit val q: TestImplicit = TestImplicitImpl1
}

trait TestImplicitClient {
  def fu(implicit ti: TestImplicit): Unit
}

object TestImplicitClient extends TestImplicitClient {
  override def fu(implicit ti: TestImplicit): Unit = {
    println("client")
    ti.f("param")
  }
}

class ClientOfClient {

  implicit def cocti: TestImplicit = {
    ImplDefinition.q
  }

  def t():Unit =
    TestImplicitClient.fu
}

object ImplTest {
  def test(): Unit = {
    implicit val ti2 = TestImplicitImpl2
    new ClientOfClient {
      override def cocti = ti2
    }.t()
  }
}

ImplTest.test() // 2: param