【问题标题】:How to override an implicit value, that is imported?如何覆盖导入的隐式值?
【发布时间】:2018-07-30 21:27:32
【问题描述】:

我已经尝试过How to override an implicit value? 中描述的解决方案,但它没有帮助。这是一个代码示例。

具有 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(类似于使用Future 的API)来决定:

object ClientOfClient {

  import somepackage.ImplDefinition.q

  def t():Unit =
    TestImplicitClient.fu
}

现在在测试中,我想使用这个ClientOfClient.t(),但我需要覆盖隐式,并改用TestImplicitImpl2。背后的主要思想 - 隐式应该由 API 的客户端定义/覆盖,而不是 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() 方法中添加额外的隐式参数。

【问题讨论】:

    标签: scala implicit overriding


    【解决方案1】:

    只要你有一个单例对象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”),而不是直接使用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
    

    【讨论】:

    • 它有效,非常感谢。但它使客户端的 API 变得更加复杂。有没有办法让客户端只覆盖隐式,例如:implicit val ti2 = TestImplicitImpl2 而无需添加一些额外的步骤?并且同时允许客户端使用默认隐式,直接在ClientOfClient中定义?
    • @Alexandr 您可以使用从外部提供的隐式,但如果没有提供则回退到默认值。例如def t()(implicit ti: TestImplicit = TestImplicitImpl1) = ...,然后在test 中使用ClientOfClient.t()
    • 这是客户的理想解决方案:) 我完全忘记了默认值。不幸的是,我们仍然必须采用我们的客户端 API,但无论如何,这似乎是最好的解决方案。 Спасибо:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-02
    • 2014-11-11
    • 1970-01-01
    • 2018-01-02
    • 2017-02-20
    • 1970-01-01
    • 2018-05-28
    相关资源
    最近更新 更多