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表述并分享给所有开发人员。这里的设计目标有“可开关电炉”和“咖啡机感应设备”

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

 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子库:

 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 }

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

 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。不由我控制。这恰恰彰显了依赖注入的作用。

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

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。

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏较真的前端

[译] 调试 RxJS 第2部分: 日志篇

1494
来自专栏跟着阿笨一起玩NET

如何防止你的UI出现了假死吗?

如果应用程序在UI线程上执行非UI线程的耗时处理时,会使应用程序界面的运行显得缓慢而迟钝,有时会出现应用程序“UI界面假死”的现象,这也会引起用户的不满。

220
来自专栏hrscy

iOS多线程

进程是指系统中正在运行的一个应用程序。每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。

613
来自专栏Golang语言社区

通俗的解释一下什么是 RPC 框架?

首先了解什么叫RPC,为什么要RPC,RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不...

1005
来自专栏GreenLeaves

Oracle PL/SQL编程之包(packages)

1、简介 包用于在逻辑上组合过程和函数,它由包规范和包体组成。 我们可以使用create package来创建包,代码如下: ? ok,包创建完成,通过包的代码...

1755
来自专栏老秦求学

汇编语言之中断学习

计算机经常会遇到异常,会产生中断,发出中断请求。中断分为内中断和外中断。下面主要介绍的是内中断。   内中断,cpu什么时候会发出中断信号呢?一般有一下四种情况...

37014
来自专栏用户2442861的专栏

深入浅出 RPC - 深入篇

http://blog.csdn.net/mindfloating/article/details/39474123

481
来自专栏猿天地

Spring Boot 使用WebAsyncTask异步返回结果

在Spring Boot中(Spring MVC)下请求默认都是同步的,一个请求过去到结束都是由一个线程负责的,很多时候为了能够提高吞吐量,需要将一些操作异步化...

1202
来自专栏代码世界

Python之装饰器

函数篇--装饰器 装饰器的主要功能: 一个闭包函数。 装饰器的主要功能: 在不改变函数调用方式的基础上在函数的前、后添加功能。 开放封闭原则: 1.对扩展是开放...

2748
来自专栏蓝天

MOOON-scheduler问题讨论:消息如何传递回去和主动发送出去?

2.所有Service进程不加载dispachter,只内核加载一份dispatcher,消息发出时,总是需要通过内核

562

扫码关注云+社区