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依赖注入的原理、组合、结构设计:
还说那个咖啡机例子:包括了一个开关设备、咖啡感应器;如果感应到壶里有咖啡的话,按下开关咖啡机可以开启加热。
下面是功能抽象描述,它们是按照开发条件和环境需要进行具体细分的。细分程度要确保每项功能都可以独立完成编程。
- trait OnOffDevice {
- def on: Unit
- def off: Unit
- }
- trait SensorDevice {
- def isCoffeePresent: Boolean
- }
这就是一个抽象层。所有开发人员都必须按照这层的功能描述来编程,所谓编程细节依赖与抽象层要求。
我们现在就可以不用理会以上功能是否已经实现,立即进入上层模块的功能组合了。我们只需要申明依赖项目,先从最简单的开始,假如我现在只需要引用OnOffDevice一项依赖的话,可以在伴生对象(companion object)这样申明操作Reader(primitive reader):
- object OnOffDevice {
- def on: Reader[OnOffDevice, String] = Reader(_.on)
- def off: Reader[OnOffDevice, String] = Reader(_.off)
- }
由于只有一个依赖,我们可以直接申明功能Reader,把on,off两个函数变成Primitive Reader。假如我们需要函数运算结果的话,只要注入OnOffDevice实例。由于Reader是个Monad,我们可以用map这样写:
- object OnOffDevice {
- def onOffDevice: Reader[OnOffDevice,OnOffDevice] = Reader(identity)
- def on: Reader[OnOffDevice,String] = onOffDevice map { _.on }
- def off: Reader[OnOffDevice,String] = onOffDevice map { _.off }
- }
我们用identity构建了一个基础(primitive)Reader,然后以这个基础Reader用map组合成我们需要的功能函数Reader。现在的on,off函数款式(signature)和前面的定义是一样的。现在我们可以实现这些Reader功能:
- object OnOffService {
- def on = for {
- ison <- OnOffDevice.on
- } yield ison
- def off = for {
- isoff <- OnOffDevice.off
- } yield isoff
- }
我们抽象化了OnOffDevice,不需要实现依赖项目就可以直接使用这些Reader功能函数:
- ef trigger = OnOffService.on //> trigger: => scalaz.Kleisli[scalaz.Id.Id,OnOffDevice,String]
假如现在实现了OnOffDevice实例:
- class OnOffDeviceImpl extends OnOffDevice {
- def on = "MockDevice.On"
- def off = "MockDevice.Off"
- }
我们可以在最终运行中注入实现的依赖实例来获取最终结果:
- object MockOnOffDevice extends OnOffDeviceImpl
- def trigger = OnOffService.on //> trigger: => scalaz.Kleisli[scalaz.Id.Id,Exercises.reader1.OnOffDevice,String
- //| ]
- val result = trigger(MockOnOffDevice) //> result : scalaz.Id.Id[String] = SomeDevice.On
- result === "SomeDevice.On" //> res0: Boolean = true
我们看到,对运算结果注入依赖实例后就能得出具体的运算值。
那如果我们需要两项依赖呢?
- trait OnOffDevice {
- def on: String
- def off: String
- }
- trait SensorDevice {
- def isCoffeePresent: Boolean
- }
我们可以把两个依赖组成一个更大的功能,一个更高一层的依赖:
- rait Device { //高层组合依赖
- def onOffDevice: OnOffDevice //具体依赖
- def sensorDevice: SensorDevice //具体依赖
- }
- object Device {
- val device = Reader[Device,Device](identity)
- val onOffDevice = device map {_.onOffDevice}
- val sensorDevice = device map {_.sensorDevice}
- }
所有Reader的注入依赖现在变成Device类型了。依赖的功能Reader变成这样:
- object OnOffDevice {
- import Device.onOffDevice
- def on: Reader[Device,String] = onOffDevice map { _.on }
- def off: Reader[Device,String] = onOffDevice map { _.off }
- }
- object SensorDevice {
- import Device.sensorDevice
- def isCoffeePresent: Reader[Device,Boolean] = sensorDevice map { _.isCoffeePresent }
- }
我们看到原来的Reader实现细节编码是不需要改变的,如OnOffService。
假设我们获得了下面的功能实现程序:
- class OnOffDeviceImpl extends OnOffDevice {
- def on = "SomeDevice.On"
- def off = "SomeDevice.Off"
- }
- class SensorDeviceImpl extends SensorDevice {
- def isCoffeePresent = true
- }
由于这次的依赖注入是Device类型的,所以我们需要获取Device实例用来注入到结果Reader:
- object MockOnOffDevice extends OnOffDeviceImpl
- object MockSensorDevice extends SensorDeviceImpl
- trait DeviceImpl extends Device {
- def onOffDevice = new OnOffDevice {
- def on = MockOnOffDevice.on
- def off = MockOnOffDevice.off
- }
- def sensorDevice = new SensorDevice {
- def isCoffeePresent = MockSensorDevice.isCoffeePresent
- }
- }
现在这个DeviceImpl是一个Device实例,我们可以把它注入到Reader得出运算结果,还是先运行上面的例子:
- object MockDevice extends Device with DeviceImpl
- def trigger = OnOffService.on //> trigger: => scalaz.Kleisli[scalaz.Id.Id,Exercises.reader2.Device,String]
- val result = trigger(MockDevice) //> result : scalaz.Id.Id[String] = SomeDevice.On
这次是用Device实例注入得出结果的。
我们还可以把依赖项目独立分别定义,这样可以更灵活的组合Device:
- trait OnOffComponent {
- def onOffDevice: OnOffDevice
- }
- trait SensorComponent {
- def sensorDevice: SensorDevice
- }
- trait Device extends OnOffComponent with SensorComponent
这样我们可以把两个依赖分开来格式化后组合成Device实例,因为我们的注入依赖现在是Device类型的了:
- object MockOnOffDevice extends OnOffDeviceImpl
- object MockSensorDevice extends SensorDeviceImpl
- trait OnOffFunctions extends OnOffComponent {
- def onOffDevice = MockOnOffDevice
- }
- trait SensorFunctions extends SensorComponent {
- def sensorDevice = MockSensorDevice
- }
完成了Device实例的组合后可以通过注入Device实例来得取运算结果:
- object MockDevice extends Device with OnOffFunctions with SensorFunctions
- def trigger =
- if (SensorService.isCoffeePresent(MockDevice))
- OnOffService.on(MockDevice)
- else
- OnOffService.off(MockDevice) //> trigger: => scalaz.Id.Id[String]
- trigger //> res0: scalaz.Id.Id[String] = SomeDevice.On
如果换另一个版本的SensorDevice实现:
- class SensorDeviceImpl extends SensorDevice {
- def isCoffeePresent = false
- }
重新运行:
- object MockDevice extends Device with OnOffFunctions with SensorFunctions
- def trigger =
- if (SensorService.isCoffeePresent(MockDevice))
- OnOffService.on(MockDevice)
- else
- OnOffService.off(MockDevice) //> trigger: => scalaz.Id.Id[String]
- trigger //> res0: scalaz.Id.Id[String] = SomeDevice.Off
正确反应了新的运算结果。
从以上的示范中我们看到了依赖的层次结构以及Reader搭配。我们可以用多层结构来精简基础Reader。但多层式的依赖结构统一了注入依赖类型,最后注入时就无法拆分依赖类型,又会弱化依赖的组合灵活性。所以在组织依赖时应该注意确定在自己的程序中将会使用到所有依赖。这样调用统一的一种注入类型就足够了。
下面再增添多一个依赖:增加一个电源制式检测功能,只有US制式的电源才能启动咖啡机。现在全部功能的抽象描述如下:
- trait OnOffDevice {
- def on: String
- def off: String
- }
- trait SensorDevice {
- def isCoffeePresent: Boolean
- }
- trait PowerConfig {
- def getPowerVolts(country: String): Int
- def isUSStandard(volt: Int): Boolean
- }
我们再试试增加多一层结构:
- trait Device extends OnOffComponent with SensorComponent
- trait DeviceComponent {
- def onOffDevice: OnOffDevice
- def sensorDevice: SensorDevice
- }
- trait PowerComponent {
- def powerConfig: PowerConfig
- }
- trait Appliance extends DeviceComponent with PowerComponent
- object Appliance {
- val appliance = Reader[Appliance,Appliance](identity)
- val onOffDevice = appliance map {_.onOffDevice}
- val sensorDevice = appliance map {_.sensorDevice}
- val powerConfig = appliance map {_.powerConfig}
- }
Appliance是更高层依赖。
下面还是保留了原来的Reader实现编码不变,除了import:
- object OnOffDevice {
- import Appliance.onOffDevice
- def on: Reader[Appliance,String] = onOffDevice map { _.on }
- def off: Reader[Appliance,String] = onOffDevice map { _.off }
- }
- object SensorDevice {
- import Appliance.sensorDevice
- def isCoffeePresent: Reader[Appliance,Boolean] = sensorDevice map { _.isCoffeePresent }
- }
- object PowerConfig {
- import Appliance.powerConfig
- def getPowerVolts(country: String) = powerConfig map {_.getPowerVolts(country)}
- def isUSStandard(volts: Int) = powerConfig map {_.isUSStandard(volts)}
- }
现在注入依赖的类型变成了Appliance。原来Reader功能实现代码还是不用改变:
- object OnOffService {
- def on = for {
- ison <- OnOffDevice.on
- } yield ison
- def off = for {
- isoff <- OnOffDevice.off
- } yield isoff
- }
- object SensorService {
- def isCoffeePresent = for {
- hasCoffee <- SensorDevice.isCoffeePresent
- } yield hasCoffee
- }
- object PowerService {
- def isUSStandard(country: String) = for {
- is110v <- PowerConfig.getPowerVolts(country)
- isUSS <- PowerConfig.isUSStandard(is110v)
- } yield isUSS
- }
假如增加了新功能实现程序:
- class OnOffDeviceImpl extends OnOffDevice {
- def on = "SomeDevice.On"
- def off = "SomeDevice.Off"
- }
- class SensorDeviceImpl extends SensorDevice {
- def isCoffeePresent = true
- }
- class PowerConfigImpl extends PowerConfig {
- def getPowerVolts(country: String) = country match {
- case "USA" =>
- case "UK" =>
- case "HK" =>
- case "CHN" =>
- case _ =>
- }
- def isUSStandard(volts: Int) = volts ===
- }
同样,需要把这些实例转成Appliance类型:
- object MockOnOffDevice extends OnOffDeviceImpl
- object MockSensorDevice extends SensorDeviceImpl
- object MockPowerConfig extends PowerConfigImpl
- trait OnOffFunctions extends OnOffComponent {
- def onOffDevice = MockOnOffDevice
- }
- trait SensorFunctions extends SensorComponent {
- def sensorDevice = MockSensorDevice
- }
- trait DeviceFunctions extends DeviceComponent {
- def onOffDevice = MockOnOffDevice
- def sensorDevice = MockSensorDevice
- }
- trait PowerFunctions extends PowerComponent {
- def powerConfig = MockPowerConfig
- }
再直接进行Appliance实例组合:
- object MockAppliance extends Appliance with DeviceFunctions with PowerFunctions
运行后注入Appliance实例得出结果:
- def trigger =
- if ((PowerService.isUSStandard("CHN")(MockAppliance))
- && (SensorService.isCoffeePresent(MockAppliance)))
- OnOffService.on(MockAppliance)
- else
- OnOffService.off(MockAppliance) //> trigger: => scalaz.Id.Id[String]
- trigger //> res0: scalaz.Id.Id[String] = SomeDevice.On
下面是这段程序的源代码,提供给大家作为参考:
- package Exercises
- import scalaz._
- import Scalaz._
- object reader3 {
- trait OnOffDevice {
- def on: String
- def off: String
- }
- trait SensorDevice {
- def isCoffeePresent: Boolean
- }
- trait PowerConfig {
- def getPowerVolts(country: String): Int
- def isUSStandard(volt: Int): Boolean
- }
- trait OnOffComponent {
- def onOffDevice: OnOffDevice
- }
- trait SensorComponent {
- def sensorDevice: SensorDevice
- }
- trait Device extends OnOffComponent with SensorComponent
- trait DeviceComponent {
- def onOffDevice: OnOffDevice
- def sensorDevice: SensorDevice
- }
- trait PowerComponent {
- def powerConfig: PowerConfig
- }
- trait Appliance extends DeviceComponent with PowerComponent
- object Appliance {
- val appliance = Reader[Appliance,Appliance](identity)
- val onOffDevice = appliance map {_.onOffDevice}
- val sensorDevice = appliance map {_.sensorDevice}
- val powerConfig = appliance map {_.powerConfig}
- }
- object OnOffDevice {
- import Appliance.onOffDevice
- def on: Reader[Appliance,String] = onOffDevice map { _.on }
- def off: Reader[Appliance,String] = onOffDevice map { _.off }
- }
- object SensorDevice {
- import Appliance.sensorDevice
- def isCoffeePresent: Reader[Appliance,Boolean] = sensorDevice map { _.isCoffeePresent }
- }
- object PowerConfig {
- import Appliance.powerConfig
- def getPowerVolts(country: String) = powerConfig map {_.getPowerVolts(country)}
- def isUSStandard(volts: Int) = powerConfig map {_.isUSStandard(volts)}
- }
- object OnOffService {
- def on = for {
- ison <- OnOffDevice.on
- } yield ison
- def off = for {
- isoff <- OnOffDevice.off
- } yield isoff
- }
- object SensorService {
- def isCoffeePresent = for {
- hasCoffee <- SensorDevice.isCoffeePresent
- } yield hasCoffee
- }
- object PowerService {
- def isUSStandard(country: String) = for {
- is110v <- PowerConfig.getPowerVolts(country)
- isUSS <- PowerConfig.isUSStandard(is110v)
- } yield isUSS
- }
- class OnOffDeviceImpl extends OnOffDevice {
- def on = "SomeDevice.On"
- def off = "SomeDevice.Off"
- }
- class SensorDeviceImpl extends SensorDevice {
- def isCoffeePresent = true
- }
- class PowerConfigImpl extends PowerConfig {
- def getPowerVolts(country: String) = country match {
- case "USA" =>
- case "UK" =>
- case "HK" =>
- case "CHN" =>
- case _ =>
- }
- def isUSStandard(volts: Int) = volts ===
- }
- object MockOnOffDevice extends OnOffDeviceImpl
- object MockSensorDevice extends SensorDeviceImpl
- object MockPowerConfig extends PowerConfigImpl
- trait OnOffFunctions extends OnOffComponent {
- def onOffDevice = MockOnOffDevice
- }
- trait SensorFunctions extends SensorComponent {
- def sensorDevice = MockSensorDevice
- }
- trait DeviceFunctions extends DeviceComponent {
- def onOffDevice = MockOnOffDevice
- def sensorDevice = MockSensorDevice
- }
- trait PowerFunctions extends PowerComponent {
- def powerConfig = MockPowerConfig
- }
- object MockAppliance extends Appliance with DeviceFunctions with PowerFunctions
- def trigger =
- if ((PowerService.isUSStandard("CHN")(MockAppliance))
- && (SensorService.isCoffeePresent(MockAppliance)))
- OnOffService.on(MockAppliance)
- else
- OnOffService.off(MockAppliance) //> trigger: => scalaz.Id.Id[String]
- trigger //> res0: scalaz.Id.Id[String] = SomeDevice.On
- }
Scalaz(16)- Monad:依赖注入-Dependency Injection By Reader Monad的更多相关文章
- 控制反转Inversion of Control (IoC) 与 依赖注入Dependency Injection (DI)
控制反转和依赖注入 控制反转和依赖注入是两个密不可分的方法用来分离你应用程序中的依赖性.控制反转Inversion of Control (IoC) 意味着一个对象不会新创建一个对象并依赖着它来完成工 ...
- 简明依赖注入(Dependency Injection)
前言 这是因特奈特上面不知道第几万篇讲依赖注入(Dependency Injection)的文章,但是说明白的却寥寥无几,这篇文章尝试控制字数同时不做大多数. 首先,依赖注入的是一件很简单的事情. 为 ...
- 14.AutoMapper 之依赖注入(Dependency Injection)
https://www.jianshu.com/p/f66447282780 依赖注入(Dependency Injection) AutoMapper支持使用静态服务定位构建自定义值解析器和自定 ...
- 依赖注入 | Dependency Injection
原文链接: Angular Dependency Injection翻译人员: 铁锚翻译时间: 2014年02月10日说明: 译者认为,本文中所有名词性的"依赖" 都可以理解为 & ...
- Spring点滴七:Spring中依赖注入(Dependency Injection:DI)
Spring机制中主要有两种依赖注入:Constructor-based Dependency Injection(基于构造方法依赖注入) 和 Setter-based Dependency Inje ...
- 设计模式之————依赖注入(Dependency Injection)与控制反转(Inversion of Controller)
参考链接: 依赖注入(DI) or 控制反转(IoC) laravel 学习笔记 —— 神奇的服务容器 PHP 依赖注入,从此不再考虑加载顺序 名词解释 IoC(Inversion of Contro ...
- 理解依赖注入(Dependency Injection)
理解依赖注入 Yii2.0 使用了依赖注入的思想.正是使用这种模式,使得Yii2异常灵活和强大.千万不要以为这是很玄乎的东西,看完下面的两个例子就懂了. class SessionStorage { ...
- AngularJS - 依赖注入(Dependency Injection)
点击查看AngularJS系列目录 转载请注明出处:http://www.cnblogs.com/leosx/ 依赖注入 依赖注入是软件设计模式中的一部分,用于处理组件是如何得到它说依赖的其它组件的. ...
- Spring之对象依赖关系(依赖注入Dependency Injection)
承接上篇: Spring中,如何给对象的属性赋值: 1:通过构造函数,如下所示: <!-- 1:构造函数赋初始值 --><bean id="user1" clas ...
随机推荐
- Java程序员的日常—— 基于类的策略模式、List<?>与List、泛型编译警告、同比和环比
早晨起得太早,昨晚睡得太晚,一天都迷迷糊糊的.中午虽然睡了半个小时,可是依然没有缓过来.整个下午都在混沌中....不过今天下载了一款手游--<剑侠情缘>,感觉不错,喜欢这种类型的游戏. 今 ...
- Atitit.eclipse 4.3 4.4 4.5 4.6新特性
Atitit intellij idea的使用总结attilax 1. ideaIC-2016.2.4.exe1 1.1. Ij vs eclipse市场份额1 1.2. Ij的优点(方便的支持gro ...
- Android 代码混淆之部分类不混淆的技巧
在编写Android程序之后,我们通常要代码进行混淆编码,这样才能保证市场上我们的应用不会被别人进行反编译,然后破解,所以此时需要在发布正式版本的时候,有一些类事不能混淆的,比如实现了 Seriali ...
- Linq 查询结果 可能遵循 2 º,2¹,2 ²,......增长计算
static void Main(string[] args) { , , , , , , , , }; var obj = from item in array orderby item ascen ...
- Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE
1. 前言 Web端即时通讯技术因受限于浏览器的设计限制,一直以来实现起来并不容易,主流的Web端即时通讯方案大致有4种:传统Ajax短轮询.Comet技术.WebSocket技术.SSE(Serve ...
- Express调用mssql驱动公共类dbHelper
直接上代码: /** * Created by chaozhou on 2015/9/18. */var mssql = require('mssql');var user = "sa&qu ...
- Yii的学习(2)--数据访问对象 (DAO)
摘自Yii官网:http://www.yiiframework.com/doc/guide/1.1/zh_cn/database.dao Yii提供了强大的数据库编程支持.Yii数据访问对象(DAO) ...
- 【Android】学习记录<1> -- 初识ffmpeg
工作需要用到ffmpeg来进行Android的软编码,对这玩意儿一点都不了解,做个学习记录先. FFmpeg:http://www.ffmpeg.org Fmpeg is the leading mu ...
- Windows Azure HandBook (1) IaaS相关技术
<Windows Azure Platform 系列文章目录> 1.Microsoft Azure底层是否由System Center和Hyper-V构成? Microsoft Azure ...
- Elasticsearch DSL中Query与Filter的不同
Elasticsearch支持很多查询方式,其中一种就是DSL,它是把请求写在JSON里面,然后进行相关的查询. 举个DSL例子 GET _search { "query": { ...