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

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

下面是大家共享的trait:

 // 可开关电炉
trait OnOffDeviceComponent {
val onOff: OnOffDevice
trait OnOffDevice {
def on: Unit
def off: Unit
}
}
//咖啡感应设备
trait SensorDeviceComponent {
val sensor: SensorDevice
trait SensorDevice {
def isCoffeePresent: Boolean
}
}

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

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

 // 咖啡机开关实现,这里是不需要电炉和咖啡感应功能实现
trait WarmerComponentImpl {
this: SensorDeviceComponent with OnOffDeviceComponent =>
//注入了SensorDeviceComponent和OnOffDeviceComponent
//解析了 sensor.isCoffeePresent, onOff.on, onOff.off
class Warmer {
def trigger = {
if (sensor.isCoffeePresent) onOff.on
else onOff.off
}
}
}

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

 // 电炉开关实现
trait OnOffDeviceComponentImpl extends OnOffDeviceComponent {
class Heater extends OnOffDevice {
def on = println("heater.on")
def off = println("heater.off")
}
}
// 感应器状态实现
trait SensorDeviceComponentImpl extends SensorDeviceComponent {
class PotSensor extends SensorDevice {
def isCoffeePresent = true
}
}

最终我把所有子库统一引用集成后就可以从中选择需要的实例进行组合了:

 // 把所有实例集成组合起来
object ComponentRegistry extends
OnOffDeviceComponentImpl with
SensorDeviceComponentImpl with
WarmerComponentImpl { val onOff = new Heater
val sensor = new PotSensor
val warmer = new Warmer
}
//运行
ComponentRegistry.warmer.trigger //> heater.on

输出结果heater.on是因为感应器的实现代码里def isCoffeePresent = true。不由我控制。这恰恰彰显了依赖注入的作用。

当然,如果其它人提供了另一个感应器状态实现:

 // 感应器状态实现
trait SensorNoCoffee extends SensorDeviceComponent {
class PotSensor extends SensorDevice {
def isCoffeePresent = false
}
}

我用SensorNoCoffee来组合:

 // 把所有实例集成起来
object ComponentRegistry extends
OnOffDeviceComponentImpl with
SensorNoCoffee with
WarmerComponentImpl { val onOff = new Heater
val sensor = new PotSensor
val warmer = new Warmer
}
 /运行
ComponentRegistry.warmer.trigger //> heater.off

现在结果变成了heater.off。如果我们有许多版本的实现程序,我们可以通过灵活配置来实现不同的功能。

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

那么用Monad Reader可以实现同样的依赖注入功能吗?

下面是功能需求trait:

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

虽然现在只有抽象trait,但我现在就可以对Warmer的功能进行编程了:

 //用Reader注入依赖OnOffDevice,SensorDevice. 只是共享的trait
trait WarmerFunctions {
def on: Reader[OnOffDevice,Unit] = Reader(OnOffDevice => OnOffDevice.on)
def off: Reader[OnOffDevice,Unit] = Reader(OnOffDevice => OnOffDevice.off)
def isCoffeePresent: Reader[SensorDevice,Boolean] = Reader(SensorDevice => SensorDevice.isCoffeePresent)
}
//功能实现。这时还没用到OnOffDevice,SensorDevice实例
object WarmerFuncImpl extends WarmerFunctions {
def thereIsCoffee = for {
hasCoffee <- isCoffeePresent
} yield hasCoffee
def warmerOn = for {
ison <- on
} yield ison
def warmerOff = for {
isoff <- off
} yield isoff
}

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

  trait Heater extends OnOffDevice {
def on = println("heater.on")
def off = println("heater.off")
}
trait PotSensor extends SensorDevice {
def isCoffeePresent = false
}

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

   object allDevices extends Heater with PotSensor

现在可以实现集成后的trigger函数。这里需要使用具体的功能实现程序:

  def trigger = {
if ( WarmerFuncImpl.thereIsCoffee(allDevices) )
WarmerFuncImpl.warmerOn(allDevices)
else
WarmerFuncImpl.warmerOff(allDevices)
} //> trigger: => scalaz.Id.Id[Unit]
//测试运行
trigger //> heater.off

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

   trait PotHasCoffee extends SensorDevice {
def isCoffeePresent = true
}

用PotHasCoffee来组合:

   object allDevices extends Heater with PotHasCoffee

再测试:

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

现在输入变成heater.on了。

似乎Monad Reader的依赖注入方式简单直接些。但Cake Pattern应该更适合团队协同开发,所以我们可以选择在局部功能开发中使用Reader,然后在大型软件集成时用Cake Pattern。

Scalaz(15)- Monad:依赖注入-Reader besides Cake的更多相关文章

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

    在上一篇讨论里我们简单的介绍了一下Cake Pattern和Reader Monad是如何实现依赖注入的.主要还是从方法上示范了如何用Cake Pattern和Reader在编程过程中解析依赖和注入依 ...

  2. AngularJS(15)-依赖注入

    AngularJS 依赖注入 什么是依赖注入 wiki 上的解释是:依赖注入(Dependency Injection,简称DI)是一种软件设计模式,在这种模式下,一个或更多的依赖(或服务)被注入(或 ...

  3. 深圳scala-meetup-20180902(2)- Future vs Task and ReaderMonad依赖注入

    在对上一次3月份的scala-meetup里我曾分享了关于Future在函数组合中的问题及如何用Monix.Task来替代.具体分析可以查阅这篇博文.在上篇示范里我们使用了Future来实现某种non ...

  4. 你真的了解字典(Dictionary)吗? C# Memory Cache 踩坑记录 .net 泛型 结构化CSS设计思维 WinForm POST上传与后台接收 高效实用的.NET开源项目 .net 笔试面试总结(3) .net 笔试面试总结(2) 依赖注入 C# RSA 加密 C#与Java AES 加密解密

    你真的了解字典(Dictionary)吗?   从一道亲身经历的面试题说起 半年前,我参加我现在所在公司的面试,面试官给了一道题,说有一个Y形的链表,知道起始节点,找出交叉节点.为了便于描述,我把上面 ...

  5. .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整-控制反转和依赖注入的使用

    再次调整项目架构是因为和群友dezhou的一次聊天,我原来的想法是项目尽量做简单点别搞太复杂了,仅使用了DbContext的注入,其他的也没有写接口耦合度很高.和dezhou聊过之后我仔细考虑了一下, ...

  6. ASP.NET Core中如影随形的”依赖注入”[下]: 历数依赖注入的N种玩法

    在对ASP.NET Core管道中关于依赖注入的两个核心对象(ServiceCollection和ServiceProvider)有了足够的认识之后,我们将关注的目光转移到编程层面.在ASP.NET ...

  7. ASP.NET Core中如影随形的”依赖注入”[上]: 从两个不同的ServiceProvider说起

    我们一致在说 ASP.NET Core广泛地使用到了依赖注入,通过前面两个系列的介绍,相信读者朋友已经体会到了这一点.由于前面两章已经涵盖了依赖注入在管道构建过程中以及管道在处理请求过程的应用,但是内 ...

  8. ASP.NET Core中的依赖注入(1):控制反转(IoC)

    ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...

  9. ASP.NET Core中的依赖注入(2):依赖注入(DI)

    IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...

随机推荐

  1. Atitit java的异常exception 结构Throwable类

    Atitit java的异常exception 结构Throwable类 1.1. Throwable类 2.StackTrace栈轨迹1 1.2. 3.cause因由1 1.3. 4.Suppres ...

  2. Js 对 浏览器 的 URL的操作

    下面是一些实例: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://ww ...

  3. 使用maven下载jar包的source和javadoc

    使用maven菜单下载sources和javadocs没什么反应,还是命令给力. 使用参数下载源码包与doc包: -DdownloadSources=true 下载源代码jar -DdownloadJ ...

  4. hibernate(四) 双向多对多映射关系

    序言 莫名长了几颗痘,真TM疼,可能是现在运动太少了,天天对着电脑,决定了,今天下午花两小时去跑步了, 现在继上一章节的一对多的映射关系讲解后,今天来讲讲多对多的映射关系把,明白了一对多,多对多个人感 ...

  5. 快速入门系列--MVC--07与HTML5移动开发的结合

    现在移动互联网的盛行,跨平台并兼容不同设备的HTML5越来越盛行,很多公司都在将自己过去的非HTML5网站应用渐进式的转化为HTML5应用,使得一套代码可以兼容不同的物理终端设备和浏览器,极大的提高了 ...

  6. .NET 程序启动调试器 .NET 测试代码耗费时间

    有些场景的.NET程序,不容易设置断点,可以用下面的方法,在.NET代码中增加启动调试器的代码: if (!Debugger.IsAttached) Debugger.Launch(); .cshar ...

  7. 《BI那点儿事》Microsoft 逻辑回归算法——预测股票的涨跌

    数据准备:一组股票历史成交数据(股票代码:601106 中国一重),起止日期:2011-01-04至今,其中变量有“开盘”.“最高”.“最低”.“收盘”.“总手”.“金额”.“涨跌”等 UPDATE ...

  8. easyui-datagrid行数据field原样输出html标签

    easyui-datagrid 绑定的行 field 原样输出html标签.处理效果如图: Html页面代码如下: ... <tr> <th field="id" ...

  9. Spring MVC 学习总结(六)——Spring+Spring MVC+MyBatis框架集成

    与SSH(Struts/Spring/Hibernate/)一样,Spring+SpringMVC+MyBatis也有一个简称SSM,Spring实现业务对象管理,Spring MVC负责请求的转发和 ...

  10. Intention.js – 动态重构 HTML 为响应式模式

    Intention.js 提供一个轻量级的和明确的方式,帮助你动态重组 HTML,成为响应式的方式.操作方法都放在了元素自己里面,所以灵活的布局看起来就似乎不会那么的抽象和凌乱. 您可以轻松地增加布局 ...