如何避免Scala中的依赖注入?

Posted

技术标签:

【中文标题】如何避免Scala中的依赖注入?【英文标题】:How to avoid dependency injection in Scala? 【发布时间】:2012-09-09 18:42:42 【问题描述】:

我阅读了Dependency Injection Without the Gymnastics PDF,这表明不需要任何花哨的 DI 框架,但它超出了我的掌握范围(至少没有具体示例)。有机会我会试试看Dependency Injection Without the Gymnastics 和Dead Simple Dependency Injection。

在 Java 中使用 Guice,如果 A 依赖于 B 和 C,而 B 和 C 都依赖于 D,则会有如下内容:

public class A 
    @Inject
    public A(B b, C c) 
        this.b = b;
        this.c = c;
    


public class B 
    @Inject
    public B(D d) 
        this.d = d;
    


public class C 
    @Inject
    public C(D d) 
        this.d = d;
    


public class D  /* ... */ 

以及一个说明使用哪个 D 实现的模块,然后只需从注入器请求 A 的实例:

A a = injector.createInstance(A.class);

鉴于上述 URL 中显示的内容,上述代码的 Scala 等效项看起来如何?

FWIW,我也在调查 https://github.com/dickwall/subcut/blob/master/GettingStarted.md,只是想了解抗 DI 解决方案。

【问题讨论】:

【参考方案1】:

隐式参数对于您描述的用例来说已经足够了。

case class A(implicit b: B, c: C)
case class B(implicit d: D)
case class C(implicit d: D)
class D  /* ... */ 

implicit val theD = new D
implicit val theB = B()
implicit val theC = C()

现在您可以通过以下方式请求A

val a = A()

【讨论】:

"在 Scala 2.10 中,您可以编写隐式类,而不是分别编写类和隐式实例。"诶?不,你不能。还是我迷茫?我认为implicit class 是关于隐式 conversions,而不是隐式 parameters Seth,你说得对,隐式类根本不能接受隐式参数。 我认为这和/或使用 Reader Monad 是最接近没有框架的方法。缺点是没有自动装配,人们必须以正确的顺序实例化对象。 这根本不是真的。隐式解析执行与“自动连接”相同的步骤。【参考方案2】:

你可以用self-types解决它。

A 依赖于 B 和 C,B 和 C 都依赖于 D

所以可以这样写:

class A 
  self: B with C => 


trait B  
  self: D => 


trait C 
  self: D => 


trait D 

然后在调用端:

val x = new A with BImpl with CImpl with DImpl

但是下面的代码不会编译,因为对 B、C、D 类的依赖没有解决:

val x = new A

【讨论】:

如果不能编译,就不是一个完整的解决方案。我想说一个完整的解决方案允许用户仅指定要使用的 D 的实现并能够创建 A 的实例。 @NoelYap,我想你误会了;倒数第二行代码编译给出所需的 A 实例。 @ScottMorrison,是的,我确实误会了。不过,我认为解决方案仍然不是那么好,因为它要求 A 的用户知道 B 和 C 对 D 的依赖性(IIUC,'with DImpl' 必须遵循其他'with'子句)。因此,它的可扩展性也不是很高(本质上,A 的用户必须执行整个依赖图的拓扑排序)。 @NoelYap new A with DImpl with BImpl with CImpl 由于衬里化是完全合法的(通常,以不同的顺序放置方面不会有问题)或者你的意思是不同的? 我认为他的意思是他必须知道 D 依赖于 B 和 C。在这个例子中这很简单,但 D 实际上可能传递地依赖于很多东西。同样这样做,您最终会得到一个大对象,其中包含所有内容(双关语)而不是单独的对象。这可能取决于您的情况,也可能无关紧要,但需要考虑。【参考方案3】:

提供这种类型的依赖注入很棘手。上面的大多数示例都要求您在实例化类的位置附近创建隐式。

我能想到的最接近的是:

class A(implicit b:B, c:C)
class B(implicit d:D)
class C(implicit d:D)
trait D  //the interface 
  def x:Unit


object Implicits 
  implicit def aFactory:A = new A
  implicit lazy val bInstance:B = new B
  implicit def cFactory:C = new C
  implicit def dFactory:D = new D 
     def x:Unit = /* some code */
  

然后在您的代码中像这样使用它:

import Implicits._

object MyApplication 
   def main(args: Array[String]):Unit = 
      val a = new A
   

如果您(例如)在测试时需要能够指定不同的版本,您可以执行以下操作:

import Implicits._

object MyApplication 

  // Define the actual implicits
  Implicits.module = new Module 
    import Implicits._

    def a = new A
    lazy val b = new B
    def c = new C
    def d = new D 
      def x = println("x")
    
  

  def main(args: Array[String]):Unit = 
    val a = new A // or val a = implicitly[A] 
  



// The contract (all elements that you need)
trait Module 
  def a: A
  def b: B
  def c: C
  def d: D


// Making the contract available as implicits
object Implicits 
  var module: Module = _

  implicit def aFactory:A = module.a
  implicit def bFactory:B = module.b
  implicit def cFactory:C = module.c
  implicit def dFactory:D = module.d

这将允许您在任何文件中简单地导入 Implicits._,并提供与原始问题中类似的工作流程。

但在大多数情况下,我不会使用这种策略。我只会在创建实例的类中提供隐式:

object MyApplication 

  implicit def a: A = new A
  implicit lazy val b: B = new B
  implicit def c: C = new C
  implicit def d: D = new D 
    def x: Unit = println("x")
  

  def main(args: Array[String]): Unit = 
    val a = implicitly[A]
    val e = new E
  



class E(implicit d:D) 
    new C

这里E 在另一个文件中定义并创建C 的实例。我们要求将D 传递给E,并且E 依赖于D 的文档(通过C)。

【讨论】:

【参考方案4】:

我认为@om-nom-nom 的答案非常接近您想要的。这是我所拥有的:

class A 
  self: B with C => 

  def sum = tripleD + doubleD


trait B  
  self: D => 

  def tripleD = x * 3


trait C 
  self: D => 

  def doubleD = x * 2


trait D extends B with C 
  val x: Int


trait E extends D 
  val x = 3


trait F extends D 
  val x = 4


val a = new A with E
val b = new A with F

println("a.sum = " + a.sum)
println("b.sum = " + b.sum)

【讨论】:

该解决方案要求 D 了解 B 和 C,即使依赖关系是相反的。

以上是关于如何避免Scala中的依赖注入?的主要内容,如果未能解决你的问题,请参考以下文章

如何避免依赖注入构造函数的疯狂?

Play framework + Scala:使用动作组合注入依赖

如何使依赖注入适用于 NestJS 中的全局异常过滤器?

避免贫血域模型如何与 ORM、依赖注入和可靠方法一起使用

玩法 2.5:模板中的依赖注入

“依赖注入”,“控制反转”是指啥?