Why use scala's cake pattern rather than abstract fields?

前端 未结 4 831
孤街浪徒
孤街浪徒 2020-12-15 04:51

I have been reading about doing Dependency Injection in scala via the cake pattern. I think I understand it but I must have missed something because I still can\'t see the

相关标签:
4条回答
  • 2020-12-15 05:12

    Think about what happens if TwitterService uses TwitterLocalCache. It would be a lot easier if TwitterService self-typed to TwitterLocalCache because TwitterService has no access to the val localCache you've declared. The Cake pattern (and self-typing) allows for us to inject in a much more universal and flexible manner (among other things, of course).

    0 讨论(0)
  • 2020-12-15 05:20

    I was unsure how the actual wiring would work, so I've adapted the simple example in the blog entry you linked to using abstract properties like you suggested.

    // =======================  
    // service interfaces  
    trait OnOffDevice {  
      def on: Unit  
      def off: Unit  
    }  
    trait SensorDevice {  
      def isCoffeePresent: Boolean  
    }  
    
    // =======================  
    // service implementations  
    class Heater extends OnOffDevice {  
      def on = println("heater.on")  
      def off = println("heater.off")  
    }  
    class PotSensor extends SensorDevice {  
      def isCoffeePresent = true  
    }  
    
    // =======================  
    // service declaring two dependencies that it wants injected  
    // via abstract fields
    abstract class Warmer() {
      val sensor: SensorDevice   
      val onOff: OnOffDevice  
    
      def trigger = {  
        if (sensor.isCoffeePresent) onOff.on  
        else onOff.off  
      }  
    }  
    
    trait PotSensorMixin {
        val sensor = new PotSensor
    }
    
    trait HeaterMixin {
        val onOff = new Heater  
    }
    
     val warmer = new Warmer with PotSensorMixin with HeaterMixin
     warmer.trigger 
    

    in this simple case it does work (so the technique you suggest is indeed usable).

    However, the same blog shows at least other three methods to achieve the same result; I think the choice is mostly about readability and personal preference. In the case of the technique you suggest IMHO the Warmer class communicates poorly its intent to have dependencies injected. Also to wire up the dependencies, I had to create two more traits (PotSensorMixin and HeaterMixin), but maybe you had a better way in mind to do it.

    0 讨论(0)
  • 2020-12-15 05:31

    Traits with self-type annotation is far more composable than old-fasioned beans with field injection, which you probably had in mind in your second snippet.

    Let's look how you will instansiate this trait:

    val productionTwitter = new TwitterClientComponent with TwitterUI with FSTwitterCache with TwitterConnection
    

    If you need to test this trait you probably write:

    val testTwitter = new TwitterClientComponent with TwitterUI with FSTwitterCache with MockConnection
    

    Hmm, a little DRY violation. Let's improve.

    trait TwitterSetup extends TwitterClientComponent with TwitterUI with FSTwitterCache
    val productionTwitter = new TwitterSetup with TwitterConnection
    val testTwitter = new TwitterSetup with MockConnection
    

    Furthermore if you have a dependency between services in your component (say UI depends on TwitterService) they will be resolved automatically by the compiler.

    0 讨论(0)
  • 2020-12-15 05:34

    In this example I think there is no big difference. Self-types can potentially bring more clarity in cases when a trait declares several abstract values, like

    trait ThreadPool {
      val minThreads: Int
      val maxThreads: Int
    }
    

    Then instead of depending on several abstract values you just declare dependency on a ThreadPool. Self-types (as used in Cake pattern) for me are just a way to declare several abstract members at once, giving those a convenient name.

    0 讨论(0)
提交回复
热议问题