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)できます.もう一つのセンサーは缶にコーヒーがあるかどうかを感知することができます.スイッチを押すと、缶にコーヒーが入っているときに電気ストーブをつけ、仕事を始めます.
次は皆さんが共有している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で記述し、すべての開発者に共有します.ここの設計目標は「開閉可能な電気ストーブ」と「コーヒーマシン誘導設備」です.
私がこのコーヒーマシンのスイッチプログラミングを担当するとします.しかし、私はどのように電気ストーブを開くか分からないし、コーヒーがあるかどうかも分からない.これらの機能はまだ開発されていないかもしれないからだ.しかし、この2つの機能はいずれも依存注入を通じて私に提供することができます.それらを使用できるようにします.
//        ,                 
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
    }
  }
}

その後、チームの他の人がその2つの依存開発を完了し、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

出力結果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を使用することができます.