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 { 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 6 }
我用SensorNoCoffee来组合:
1 // 把所有实例集成起来 2 object ComponentRegistry extends 3 OnOffDeviceComponentImpl with 4 SensorNoCoffee with 5 WarmerComponentImpl { 7 val onOff = new Heater 8 val sensor = new PotSensor 9 val warmer = new Warmer 10 }
现在结果变成了heater.off。如果我们有许多版本的实现程序,我们可以通过灵活配置来实现不同的功能。
我看Cake Pattern特别适合大型软件开发团队协同开发。
那么用Monad Reader可以实现同样的依赖注入功能吗?
下面是功能需求trait:
1 //事先统一设计的功能抽象描述,这个直接点,没有外套trait 2 trait OnOffDevice { 3 def on: Unit 4 def off: Unit 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") 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。
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/12932.html
cjava相关文章
- 控制反转、依赖注入、依赖倒置傻傻分不清楚?
- Shellter-A Shellcode 注入工具
- 什么是宽字节注入_innodb_buffer_pool_size
- Asp.net core自定义依赖注入容器,替换自带容器
- 如何用最简单的方式解释依赖注入?
- WPF 依赖注入之 Microsoft.Extensions.DependencyInjection
- Java系列 | 属性依赖注入被认为是有害的
- Elastic-Job2.1.5源码-自定义Spring标签与Spring 依赖注入无缝整合
- TP6.0 容器和依赖注入
- 浅谈Spring6之P和C命名空间注入的区别
- 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取要注入事件的 View 对象 | 通过反射获取 View 组件的事件设置方法 )
- Angular 依赖注入错误消息:ERROR Error NullInjectorError No provider for XX
- SQL注入
- .NET Core反射获取带有自定义特性的类,通过依赖注入根据Attribute元数据信息调用对应的方法
- TestNG 中使用 Guice 来进行依赖注入
- Java的依赖注入(控制反转)详解编程语言
- 如何通俗的理解spring的控制反转、依赖注入、面向切面编程等等详解编程语言
- PHP依赖注入的作用详解编程语言
- PHP实现依赖注入详解编程语言
- Spring DI(依赖注入)的实现方式:属性注入和构造注入
- Java Spring各种依赖注入注解的区别
- MSSQL显错注入技术:防范未来威胁(mssql显错注入)
- MSSQL注入技术:打开知识库大门(mssql注入知识库)
- 3MSSQL 注入攻击:黑客登台!(mssql注入 0x)
- Redis提供依赖注入解决方案(依赖注入redis)
- 高级php注入方法集锦
- PHP中防止SQL注入攻击和XSS攻击的两个简单方法
- 解析:清除SQL被注入恶意病毒代码的语句
- JavaScript中实现依赖注入的思路分享