Scalaz(15)- Monad:依赖注入-Reader besides Cake

  咱们能够用Monad Reader来实现依赖注入(dependency injection DI or IOC)功能。Scala界中比较经常使用的不附加任何Framework的依赖注入方式能够说是Cake Pattern了。如今经过Monad Reader能够实现一样功能,二者对比优势各有千秋。所谓依赖注入是指在编程时使用了某个未知实现细节的对象,但依赖注入确保这个对象在这段程序运行时已经实例化。这种需求一般是在大型软件开发时对项目进行模块化分割后虽然模块之间互有依赖,但又能够同步开发。特别是在多人协做开发时,各人开发进度不受他人影响。这主要是经过各人分享事先规划好的软件抽象描述如interface,trait等加上依赖注入实现的。咱们下面经过一个实际例子来示范Cake Pattern和Monad Reader是如何实现依赖注入的:编程

咱们来模拟一个咖啡机开关场景:有一个电炉,可开(on)可关(off)。还有一个感应器能感应罐里是否还有咖啡。按下开关时当罐里有咖啡时才开启(on)电炉,开始工做。模块化

下面是你们共享的trait:函数

 1 // 可开关电炉
 2 trait OnOffDeviceComponent {  3  val onOff: OnOffDevice  4  trait OnOffDevice {  5  def on: Unit  6  def off: Unit  7  }  8 }  9 //咖啡感应设备
10 trait SensorDeviceComponent { 11  val sensor: SensorDevice 12  trait SensorDevice { 13  def isCoffeePresent: Boolean 14  } 15 }

在总体设计时把功能要求用trait表述并分享给全部开发人员。这里的设计目标有“可开关电炉”和“咖啡机感应设备”测试

假设由我负责这个咖啡机开关编程。不过我并不知道如何开启电炉,也不知道如何肯定咖啡有否,由于这些功能可能还没开发出来呢。但这两项的功能均可以经过依赖注入提供给我。让我能使用它们:this

 1 // 咖啡机开关实现,这里是不须要电炉和咖啡感应功能实现
 2 trait WarmerComponentImpl {  3   this: SensorDeviceComponent with OnOffDeviceComponent =>
 4   //注入了SensorDeviceComponent和OnOffDeviceComponent  5   //解析了 sensor.isCoffeePresent, onOff.on, onOff.off
 6   class Warmer {  7     def trigger = {  8       if (sensor.isCoffeePresent) onOff.on  9       else onOff.off 10  } 11  } 12 }

假设后来团队其它人完成了对那两项依赖的开发并提供了bytecode子库:spa

 1 // 电炉开关实现
 2 trait OnOffDeviceComponentImpl extends OnOffDeviceComponent {  3   class Heater extends OnOffDevice {  4     def on = println("heater.on")  5     def off = println("heater.off")  6  }  7 }  8 // 感应器状态实现
 9 trait SensorDeviceComponentImpl extends SensorDeviceComponent { 10   class PotSensor extends SensorDevice { 11     def isCoffeePresent = true
12  } 13 }

最终我把全部子库统一引用集成后就能够从中选择须要的实例进行组合了:scala

 1 // 把全部实例集成组合起来
 2 object ComponentRegistry extends  3  OnOffDeviceComponentImpl with  4  SensorDeviceComponentImpl with  5  WarmerComponentImpl {  6 
 7   val onOff = new Heater  8   val sensor = new PotSensor  9   val warmer = new Warmer 10 } 11 //运行
12 ComponentRegistry.warmer.trigger                  //> heater.on

输出结果heater.on是由于感应器的实现代码里def isCoffeePresent = true。不禁我控制。这偏偏彰显了依赖注入的做用。设计

固然,若是其它人提供了另外一个感应器状态实现:code

1 // 感应器状态实现
2 trait SensorNoCoffee extends SensorDeviceComponent { 3   class PotSensor extends SensorDevice { 4     def isCoffeePresent = false
5  } 6 }

我用SensorNoCoffee来组合:对象

 1 // 把全部实例集成起来
 2 object ComponentRegistry extends  3  OnOffDeviceComponentImpl with  4  SensorNoCoffee with  5  WarmerComponentImpl {  6 
 7   val onOff = new Heater  8   val sensor = new PotSensor  9   val warmer = new Warmer 10 }
1 /运行 2 ComponentRegistry.warmer.trigger                  //> heater.off

如今结果变成了heater.off。若是咱们有许多版本的实现程序,咱们能够经过灵活配置来实现不一样的功能。

我看Cake Pattern特别适合大型软件开发团队协同开发。

那么用Monad Reader能够实现一样的依赖注入功能吗?

下面是功能需求trait:

1 //事先统一设计的功能抽象描述,这个直接点,没有外套trait
2  trait OnOffDevice { 3  def on: Unit 4  def off: Unit 5  } 6  trait SensorDevice { 7  def isCoffeePresent: Boolean 8   }

虽然如今只有抽象trait,但我如今就能够对Warmer的功能进行编程了:

 1 //用Reader注入依赖OnOffDevice,SensorDevice. 只是共享的trait
 2  trait WarmerFunctions {  3       def on: Reader[OnOffDevice,Unit] = Reader(OnOffDevice => OnOffDevice.on)  4       def off: Reader[OnOffDevice,Unit] = Reader(OnOffDevice => OnOffDevice.off)  5       def isCoffeePresent: Reader[SensorDevice,Boolean] = Reader(SensorDevice => SensorDevice.isCoffeePresent)  6  }  7 //功能实现。这时还没用到OnOffDevice,SensorDevice实例
 8   object WarmerFuncImpl extends WarmerFunctions {  9     def thereIsCoffee = for { 10       hasCoffee <- isCoffeePresent 11     } yield hasCoffee 12     def warmerOn = for { 13      ison <- on 14     } yield ison 15     def warmerOff = for { 16         isoff <- off 17     } yield isoff 18   }

 

假设这时有人完成并提交了功能实现程序:

 

1  trait Heater extends OnOffDevice { 2     def on = println("heater.on") 3     def off = println("heater.off") 4  } 5  trait PotSensor extends SensorDevice { 6     def isCoffeePresent = false
7   }

有了功能实现的bytecode后就能够把它们组合起来了:

1   object allDevices extends Heater with PotSensor

如今能够实现集成后的trigger函数。这里须要使用具体的功能实现程序:

1  def trigger = { 2       if ( WarmerFuncImpl.thereIsCoffee(allDevices) ) 3  WarmerFuncImpl.warmerOn(allDevices) 4       else
5  WarmerFuncImpl.warmerOff(allDevices) 6   }                                               //> trigger: => scalaz.Id.Id[Unit] 7   //测试运行
8   trigger                                         //> heater.off

如今trigger的结果是heater.off,这是由感应器具体实现来肯定的。固然,若是还有另外一个版本的实现程序:

1  trait PotHasCoffee extends SensorDevice { 2     def isCoffeePresent = true
3   }

用PotHasCoffee来组合:

1   object allDevices extends Heater with PotHasCoffee

再测试:

1 //测试运行
2   trigger                                         //> heater.on

如今输入变成heater.on了。

彷佛Monad Reader的依赖注入方式简单直接些。但Cake Pattern应该更适合团队协同开发,因此咱们能够选择在局部功能开发中使用Reader,而后在大型软件集成时用Cake Pattern。

相关文章
相关标签/搜索