Scalaz(16)- Monad:依赖注入-Dependency Injection By Reader Monad

  在上一篇讨论里我们简单的介绍了一下Cake Pattern和Reader Monad是如何实现依赖注入的。主要还是从方法上示范了如何用Cake Pattern和Reader在编程过程中解析依赖和注入依赖。考虑到依赖注入模式在编程中的重要性和普遍性,觉着还需要再讨论的深入一些,使依赖注入模式在FP领域里能从理论走向实际。既然我们正在scalaz的介绍系列里,所以这篇我们就着重示范Reader Monad的依赖注入方法。

  再说明一下依赖注入:我们说过在团队协作开发里能够实现软件模块的各自独立开发,原理首先是实现软件模块之间的松散耦合(decoupling)。对依赖注入的学术定义如下:Dependency Inversion Principle

1、上层模块不应该依赖下层模块,反之亦然。它们之间不应该有直接的代码引用。它们应该都依赖一个抽象的中间层,也就是共享接口(interface)。

2、抽象层不应该依赖实现细节,而细节实现则必须依赖抽象层。大家都按照共享的抽象层进行实现细节编程。

这两条足够解释何为软件模块松散耦合以及具体编码的要求了。这样来说不仅在团队协同开发,即使在个人独立开发环境下依赖注入模式也能发挥良好的作用。一是可以按需要把软件切分成功能模块独立编程。通过依赖注入模式,当下层模块进行了调整后是不会影响上层模块的;上层模块可以随时连接不同的下层功能模块实现不同的功能,比如连接一些测试环境实现上层模块的独立测试。

下面我们还是取用上期的示范例子,由简入深,逐步说明Reader依赖注入的原理、组合、结构设计:

还说那个咖啡机例子:包括了一个开关设备、咖啡感应器;如果感应到壶里有咖啡的话,按下开关咖啡机可以开启加热。

下面是功能抽象描述,它们是按照开发条件和环境需要进行具体细分的。细分程度要确保每项功能都可以独立完成编程。

1   trait OnOffDevice {
2     def on: Unit
3     def off: Unit
4   }
5   trait SensorDevice {
6     def isCoffeePresent: Boolean
7   }

这就是一个抽象层。所有开发人员都必须按照这层的功能描述来编程,所谓编程细节依赖与抽象层要求。

我们现在就可以不用理会以上功能是否已经实现,立即进入上层模块的功能组合了。我们只需要申明依赖项目,先从最简单的开始,假如我现在只需要引用OnOffDevice一项依赖的话,可以在伴生对象(companion object)这样申明操作Reader(primitive reader):

1 object OnOffDevice {
2   def on: Reader[OnOffDevice, String] = Reader(_.on)
3   def off:  Reader[OnOffDevice, String] = Reader(_.off)
4 }

由于只有一个依赖,我们可以直接申明功能Reader,把on,off两个函数变成Primitive Reader。假如我们需要函数运算结果的话,只要注入OnOffDevice实例。由于Reader是个Monad,我们可以用map这样写:

1 object OnOffDevice {
2   def onOffDevice: Reader[OnOffDevice,OnOffDevice] = Reader(identity)
3   def on: Reader[OnOffDevice,String] = onOffDevice map { _.on }
4   def off: Reader[OnOffDevice,String] = onOffDevice map { _.off }
5 }

我们用identity构建了一个基础(primitive)Reader,然后以这个基础Reader用map组合成我们需要的功能函数Reader。现在的on,off函数款式(signature)和前面的定义是一样的。现在我们可以实现这些Reader功能:

1 object OnOffService {
2     def on = for {
3         ison <- OnOffDevice.on
4     } yield ison
5     def off = for {
6         isoff <- OnOffDevice.off
7     } yield isoff
8 }

我们抽象化了OnOffDevice,不需要实现依赖项目就可以直接使用这些Reader功能函数:

1 ef trigger = OnOffService.on      //> trigger: => scalaz.Kleisli[scalaz.Id.Id,OnOffDevice,String]

假如现在实现了OnOffDevice实例:

1 class OnOffDeviceImpl extends OnOffDevice {
2     def on = "MockDevice.On"
3     def off = "MockDevice.Off"
4 }

我们可以在最终运行中注入实现的依赖实例来获取最终结果:

1 object MockOnOffDevice extends OnOffDeviceImpl
2 def trigger = OnOffService.on                     //> trigger: => scalaz.Kleisli[scalaz.Id.Id,Exercises.reader1.OnOffDevice,String
3                                                   //| ]
4 val result = trigger(MockOnOffDevice)             //> result  : scalaz.Id.Id[String] = SomeDevice.On
5 result === "SomeDevice.On"                        //> res0: Boolean = true

我们看到,对运算结果注入依赖实例后就能得出具体的运算值。

那如果我们需要两项依赖呢?

1 trait OnOffDevice {
2     def on: String
3     def off: String
4 }
5 trait SensorDevice {
6     def isCoffeePresent: Boolean
7 }

我们可以把两个依赖组成一个更大的功能,一个更高一层的依赖:

1 rait Device { //高层组合依赖
2   def onOffDevice: OnOffDevice     //具体依赖
3   def sensorDevice: SensorDevice   //具体依赖
4 }
5 object Device {
6   val device = Reader[Device,Device](identity)
7   val onOffDevice = device map {_.onOffDevice}
8   val sensorDevice = device map {_.sensorDevice}
9 }

所有Reader的注入依赖现在变成Device类型了。依赖的功能Reader变成这样:

1 object OnOffDevice {
2 import Device.onOffDevice
3     def on: Reader[Device,String] = onOffDevice map { _.on }
4     def off: Reader[Device,String] = onOffDevice map { _.off }
5 }
6 object SensorDevice {
7 import Device.sensorDevice
8   def isCoffeePresent: Reader[Device,Boolean] = sensorDevice map { _.isCoffeePresent }
9 }

我们看到原来的Reader实现细节编码是不需要改变的,如OnOffService。 假设我们获得了下面的功能实现程序:

1 class OnOffDeviceImpl extends OnOffDevice {
2     def on = "SomeDevice.On"
3     def off = "SomeDevice.Off"
4 }
5 class SensorDeviceImpl extends SensorDevice {
6     def isCoffeePresent = true
7 }

由于这次的依赖注入是Device类型的,所以我们需要获取Device实例用来注入到结果Reader:

 1 object MockOnOffDevice extends OnOffDeviceImpl
 2 object MockSensorDevice extends SensorDeviceImpl
 3 trait DeviceImpl extends Device {
 4     def onOffDevice = new OnOffDevice {
 5         def on = MockOnOffDevice.on
 6         def off = MockOnOffDevice.off
 7     }
 8     def sensorDevice = new SensorDevice {
 9         def isCoffeePresent = MockSensorDevice.isCoffeePresent
10     }
11 }

现在这个DeviceImpl是一个Device实例,我们可以把它注入到Reader得出运算结果,还是先运行上面的例子:

1 object MockDevice extends Device with DeviceImpl
2 def trigger = OnOffService.on                     //> trigger: => scalaz.Kleisli[scalaz.Id.Id,Exercises.reader2.Device,String]
3 val result = trigger(MockDevice)                  //> result  : scalaz.Id.Id[String] = SomeDevice.On

这次是用Device实例注入得出结果的。

我们还可以把依赖项目独立分别定义,这样可以更灵活的组合Device:

1 trait OnOffComponent {
2     def onOffDevice: OnOffDevice
3 }
4 trait SensorComponent {
5     def sensorDevice: SensorDevice
6 }
7 trait Device extends OnOffComponent with SensorComponent

这样我们可以把两个依赖分开来格式化后组合成Device实例,因为我们的注入依赖现在是Device类型的了:

1 object MockOnOffDevice extends OnOffDeviceImpl
2 object MockSensorDevice extends SensorDeviceImpl
3 trait OnOffFunctions extends OnOffComponent {
4     def onOffDevice = MockOnOffDevice
5 }
6 trait SensorFunctions extends SensorComponent {
7   def sensorDevice = MockSensorDevice
8 }

完成了Device实例的组合后可以通过注入Device实例来得取运算结果:

1 object MockDevice extends Device with OnOffFunctions with SensorFunctions
2 def trigger =
3   if (SensorService.isCoffeePresent(MockDevice))
4      OnOffService.on(MockDevice)
5   else
6      OnOffService.off(MockDevice)                 //> trigger: => scalaz.Id.Id[String]
7 trigger                                           //> res0: scalaz.Id.Id[String] = SomeDevice.On

如果换另一个版本的SensorDevice实现:

1 class SensorDeviceImpl extends SensorDevice {
2     def isCoffeePresent = false
3 }

重新运行:

1 object MockDevice extends Device with OnOffFunctions with SensorFunctions
2 def trigger =
3   if (SensorService.isCoffeePresent(MockDevice))
4      OnOffService.on(MockDevice)
5   else
6      OnOffService.off(MockDevice)                 //> trigger: => scalaz.Id.Id[String]
7 trigger                                           //> res0: scalaz.Id.Id[String] = SomeDevice.Off

正确反应了新的运算结果。

从以上的示范中我们看到了依赖的层次结构以及Reader搭配。我们可以用多层结构来精简基础Reader。但多层式的依赖结构统一了注入依赖类型,最后注入时就无法拆分依赖类型,又会弱化依赖的组合灵活性。所以在组织依赖时应该注意确定在自己的程序中将会使用到所有依赖。这样调用统一的一种注入类型就足够了。

下面再增添多一个依赖:增加一个电源制式检测功能,只有US制式的电源才能启动咖啡机。现在全部功能的抽象描述如下:

 1 trait OnOffDevice {
 2     def on: String
 3     def off: String
 4 }
 5 trait SensorDevice {
 6     def isCoffeePresent: Boolean
 7 }
 8 trait PowerConfig {
 9   def getPowerVolts(country: String): Int
10   def isUSStandard(volt: Int): Boolean
11 }

我们再试试增加多一层结构:

 1 trait Device extends OnOffComponent with SensorComponent
 2 trait DeviceComponent {
 3     def onOffDevice: OnOffDevice
 4     def sensorDevice: SensorDevice
 5 }
 6 trait PowerComponent {
 7     def powerConfig: PowerConfig
 8 }
 9 trait Appliance extends DeviceComponent with PowerComponent
10 object Appliance {
11   val appliance = Reader[Appliance,Appliance](identity)
12   val onOffDevice = appliance map {_.onOffDevice}
13   val sensorDevice = appliance map {_.sensorDevice}
14   val powerConfig = appliance map {_.powerConfig}
15 }

Appliance是更高层依赖。

下面还是保留了原来的Reader实现编码不变,除了import:

 1 object OnOffDevice {
 2 import Appliance.onOffDevice
 3     def on: Reader[Appliance,String] = onOffDevice map { _.on }
 4     def off: Reader[Appliance,String] = onOffDevice map { _.off }
 5 }
 6 object SensorDevice {
 7 import Appliance.sensorDevice
 8   def isCoffeePresent: Reader[Appliance,Boolean] = sensorDevice map { _.isCoffeePresent }
 9 }
10 object PowerConfig {
11 import Appliance.powerConfig
12     def getPowerVolts(country: String) = powerConfig map {_.getPowerVolts(country)}
13     def isUSStandard(volts: Int) = powerConfig map {_.isUSStandard(volts)}
14 }

现在注入依赖的类型变成了Appliance。原来Reader功能实现代码还是不用改变:

 1 object OnOffService {
 2     def on = for {
 3         ison <- OnOffDevice.on
 4     } yield ison
 5     def off = for {
 6         isoff <- OnOffDevice.off
 7     } yield isoff
 8 }
 9 object SensorService {
10     def isCoffeePresent = for {
11         hasCoffee <- SensorDevice.isCoffeePresent
12     } yield hasCoffee
13 }
14 object PowerService {
15     def isUSStandard(country: String) = for {
16         is110v <- PowerConfig.getPowerVolts(country)
17         isUSS <- PowerConfig.isUSStandard(is110v)
18     } yield isUSS
19 }

假如增加了新功能实现程序:

 1 class OnOffDeviceImpl extends OnOffDevice {
 2     def on = "SomeDevice.On"
 3     def off = "SomeDevice.Off"
 4 }
 5 class SensorDeviceImpl extends SensorDevice {
 6     def isCoffeePresent = true
 7 }
 8 class PowerConfigImpl extends PowerConfig {
 9     def getPowerVolts(country: String) = country match {
10         case "USA" => 110
11         case "UK" => 220
12         case "HK" => 220
13         case "CHN" => 110
14         case _  => 0
15     }
16     def isUSStandard(volts: Int) = volts === 110
17 }

同样,需要把这些实例转成Appliance类型: 

 1 object MockOnOffDevice extends OnOffDeviceImpl
 2 object MockSensorDevice extends SensorDeviceImpl
 3 object MockPowerConfig extends PowerConfigImpl
 4 trait OnOffFunctions extends OnOffComponent {
 5     def onOffDevice = MockOnOffDevice
 6 }
 7 trait SensorFunctions extends SensorComponent {
 8   def sensorDevice = MockSensorDevice
 9 }
10 trait DeviceFunctions extends DeviceComponent  {
11     def onOffDevice = MockOnOffDevice
12   def sensorDevice = MockSensorDevice
13 }
14 trait PowerFunctions extends PowerComponent {
15     def powerConfig = MockPowerConfig
16 }

再直接进行Appliance实例组合:

1 object MockAppliance extends Appliance with DeviceFunctions with PowerFunctions

运行后注入Appliance实例得出结果:

1 def trigger =
2   if ((PowerService.isUSStandard("CHN")(MockAppliance))
3       && (SensorService.isCoffeePresent(MockAppliance)))
4        OnOffService.on(MockAppliance)
5    else
6      OnOffService.off(MockAppliance)              //> trigger: => scalaz.Id.Id[String]
7 trigger                                           //> res0: scalaz.Id.Id[String] = SomeDevice.On

下面是这段程序的源代码,提供给大家作为参考:

  1 package Exercises
  2 import scalaz._
  3 import Scalaz._
  4 object reader3 {
  5 trait OnOffDevice {
  6     def on: String
  7     def off: String
  8 }
  9 trait SensorDevice {
 10     def isCoffeePresent: Boolean
 11 }
 12 trait PowerConfig {
 13   def getPowerVolts(country: String): Int
 14   def isUSStandard(volt: Int): Boolean
 15 }
 16 
 17 trait OnOffComponent {
 18     def onOffDevice: OnOffDevice
 19 }
 20 trait SensorComponent {
 21     def sensorDevice: SensorDevice
 22 }
 23 trait Device extends OnOffComponent with SensorComponent
 24 trait DeviceComponent {
 25     def onOffDevice: OnOffDevice
 26     def sensorDevice: SensorDevice
 27 }
 28 trait PowerComponent {
 29     def powerConfig: PowerConfig
 30 }
 31 trait Appliance extends DeviceComponent with PowerComponent
 32 object Appliance {
 33   val appliance = Reader[Appliance,Appliance](identity)
 34   val onOffDevice = appliance map {_.onOffDevice}
 35   val sensorDevice = appliance map {_.sensorDevice}
 36   val powerConfig = appliance map {_.powerConfig}
 37 }
 38 object OnOffDevice {
 39 import Appliance.onOffDevice
 40     def on: Reader[Appliance,String] = onOffDevice map { _.on }
 41     def off: Reader[Appliance,String] = onOffDevice map { _.off }
 42 }
 43 object SensorDevice {
 44 import Appliance.sensorDevice
 45   def isCoffeePresent: Reader[Appliance,Boolean] = sensorDevice map { _.isCoffeePresent }
 46 }
 47 object PowerConfig {
 48 import Appliance.powerConfig
 49     def getPowerVolts(country: String) = powerConfig map {_.getPowerVolts(country)}
 50     def isUSStandard(volts: Int) = powerConfig map {_.isUSStandard(volts)}
 51 }
 52 object OnOffService {
 53     def on = for {
 54         ison <- OnOffDevice.on
 55     } yield ison
 56     def off = for {
 57         isoff <- OnOffDevice.off
 58     } yield isoff
 59 }
 60 object SensorService {
 61     def isCoffeePresent = for {
 62         hasCoffee <- SensorDevice.isCoffeePresent
 63     } yield hasCoffee
 64 }
 65 object PowerService {
 66     def isUSStandard(country: String) = for {
 67         is110v <- PowerConfig.getPowerVolts(country)
 68         isUSS <- PowerConfig.isUSStandard(is110v)
 69     } yield isUSS
 70 }
 71 class OnOffDeviceImpl extends OnOffDevice {
 72     def on = "SomeDevice.On"
 73     def off = "SomeDevice.Off"
 74 }
 75 class SensorDeviceImpl extends SensorDevice {
 76     def isCoffeePresent = true
 77 }
 78 class PowerConfigImpl extends PowerConfig {
 79     def getPowerVolts(country: String) = country match {
 80         case "USA" => 110
 81         case "UK" => 220
 82         case "HK" => 220
 83         case "CHN" => 110
 84         case _  => 0
 85     }
 86     def isUSStandard(volts: Int) = volts === 110
 87 }
 88 object MockOnOffDevice extends OnOffDeviceImpl
 89 object MockSensorDevice extends SensorDeviceImpl
 90 object MockPowerConfig extends PowerConfigImpl
 91 trait OnOffFunctions extends OnOffComponent {
 92     def onOffDevice = MockOnOffDevice
 93 }
 94 trait SensorFunctions extends SensorComponent {
 95   def sensorDevice = MockSensorDevice
 96 }
 97 trait DeviceFunctions extends DeviceComponent  {
 98     def onOffDevice = MockOnOffDevice
 99   def sensorDevice = MockSensorDevice
100 }
101 trait PowerFunctions extends PowerComponent {
102     def powerConfig = MockPowerConfig
103 }
104 object MockAppliance extends Appliance with DeviceFunctions with PowerFunctions
105 def trigger =
106   if ((PowerService.isUSStandard("CHN")(MockAppliance))
107       && (SensorService.isCoffeePresent(MockAppliance)))
108        OnOffService.on(MockAppliance)
109    else
110      OnOffService.off(MockAppliance)              //> trigger: => scalaz.Id.Id[String]
111 trigger                                           //> res0: scalaz.Id.Id[String] = SomeDevice.On
112 }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏zhisheng

0Day技术分析-2-栈溢出原理

栈溢出原理 1 什么是栈 1.1. 缓冲区 我们向一个杯子里倒水,如果我们倒的水超出了杯子的容量,水就会溢出来。 在我们写程序的时候也可能会用到一些临时的变量 ...

3376
来自专栏锦小年的博客

python学习笔记1-理解Python语言

本章主要介绍一下python语言的相关知识,包括其语言类型、作为面向对象的3大特性以及5大原则,在末尾介绍了其优缺点。 python是解释型的脚本语言 解...

1809
来自专栏用户画像

4.2.4 文件系统实现

线性列表就是把文件名组织成一个线性表,查找的时候依次与线性表中每个表项进行比较。若把文件名按序排列使用折半查找法 可以降低平均的查找时间,但是建立新文件时会增加...

652
来自专栏salesforce零基础学习

salesforce 零基础开发入门学习(三)sObject简单介绍以及简单DML操作(SOQL)

salesforce中对于数据库操作和JAVA等语言对于数据库操作是有一定区别的。salesforce中的数据库使用的是Force.com 平台的数据库,数据表...

2085
来自专栏iOS 开发杂谈

iOS 编译过程

iOS 编译采用 Clang 作为编译器前端,LLVM 作为编译器后端,编译器前端负责语法分析,语义分析,生成生成中间码 (LLVM IR),在这个过程中,会进...

972
来自专栏C/C++基础

网络字节序与主机字节序转换

在Linux网络编程中,经常碰到网络字节序与主机字节序的相互转换。说到网络字节序与主机字节序需要清晰了解以下几个概念。

643
来自专栏walterlv - 吕毅的博客

.NET 使用 XPath 来读写 XML 文件

发布于 2018-06-24 13:10 更新于 2018-09...

661
来自专栏分布式系统和大数据处理

.Net自定义应用程序配置

几乎所有的应用程序都离不开配置,有时候我们会将配置信息存在数据库中(例如大家可能常会见到名为Config这样的表);更多时候,我们会将配置写在Web.confi...

693
来自专栏分布式系统和大数据处理

.Net 项目代码风格参考

代码风格没有正确与否,重要的是整齐划一,这是我拟的一份《.Net 项目代码风格参考》,供大家参考。

1182
来自专栏Jerry的SAP技术分享

ERP和C4C中的function location

SAP ERP里的Functional Locations,下载到SAP Cloud for Customer后成为类型为‘Functional Locatio...

782

扫码关注云+社区