How to avoid dependency injection in Scala?

前端 未结 4 1891
爱一瞬间的悲伤
爱一瞬间的悲伤 2021-02-10 06:34

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

相关标签:
4条回答
  • 2021-02-10 07:05

    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
    
    0 讨论(0)
  • 2021-02-10 07:09

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

    0 讨论(0)
  • 2021-02-10 07:17

    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)
    
    0 讨论(0)
  • 2021-02-10 07:25

    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()
    
    0 讨论(0)
提交回复
热议问题