咱們能夠用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表述並分享給全部開發人員。這裏的設計目標有「可開關電爐」和「咖啡機感應設備」測試
假設由我負責這個咖啡機開關編程。不過我並不知道如何開啓電爐,也不知道如何肯定咖啡有否,由於這些功能可能還沒開發出來呢。但這兩項的功能均可以經過依賴注入提供給我。讓我能使用它們:this
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子庫:spa
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 }
最終我把全部子庫統一引用集成後就能夠從中選擇須要的實例進行組合了:scala
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。不禁我控制。這偏偏彰顯了依賴注入的做用。設計
固然,若是其它人提供了另外一個感應器狀態實現:code
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。