How to avoid dependency injection in Scala?

孤人 提交于 2019-12-21 05:18:13

问题


I read Dependency Injection Without the Gymnastics PDF which indicates there's no need for any fancy DI framework, but it's beyond my grasp (at least without concrete examples). I'll try watching Dependency Injection Without the Gymnastics and Dead Simple Dependency Injection when I have a chance.

Using Guice in Java, if A depends on both B and C and both B and C depend on D, one would have something like:

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 { /* ... */ }

and a module that says which implementation of D to use, then one would just ask for an instance of A from the injector:

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

Given what's presented in the URLs above, how would the Scala-equivalent of the above code look?

FWIW, I'm also investigating https://github.com/dickwall/subcut/blob/master/GettingStarted.md and am simply trying to understand the anti-DI solution.


回答1:


Implicit parameters are completely sufficient for the use case you're describing.

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()

Now you can ask for an A just by:

val a = A()



回答2:


You may solve it with self-types.

A depends on both B and C and both B and C depend on D

so one could write this like that:

class A {
  self: B with C => 
}

trait B { 
  self: D => 
}

trait C {
  self: D => 
}

trait D {}

and then on a call side:

val x = new A with BImpl with CImpl with DImpl

but code below won't compile, because dependencies on B,C,D classes not resolved:

val x = new A



回答3:


It's tricky to provide that type of dependency injection. Most of the above examples require you to create the implicits near where the classes are instantiated.

Closest I could come up with is:

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 */}
  }
}

And then in your code you use it like this:

import Implicits._

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

If you need to be able to specify different versions when you (for example) are testing, you could do something like this:

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
}

This would allow you to simply import Implicits._ in any file and would provide a similar workflow as the one in the original question.

In most cases however I would not use this tactic. I would simply make the implicit available in classes that create instances:

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
}

Here E is defined in another file and creates an instance of C. We require D to be passed to E and with that document that E depends on D (via C).




回答4:


I think @om-nom-nom's answer is quite close to what you want. Here is what I've got:

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)


来源:https://stackoverflow.com/questions/12341867/how-to-avoid-dependency-injection-in-scala

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!