25

私はケーキパターンを介してscalaで依存性注入を行うことについて読んでいます。私はそれを理解していると思いますが、まだ要点が見えないので、何かを見逃しているに違いありません! 単なる抽象フィールドではなく、自己型を介して依存関係を宣言する方が望ましいのはなぜですか?

Programming Scala の例ではTwitterClientComponent、cake パターンを使用して次のように依存関係を宣言しています。

//other trait declarations elided for clarity
...

trait TwitterClientComponent {

  self: TwitterClientUIComponent with
        TwitterLocalCacheComponent with
        TwitterServiceComponent =>

  val client: TwitterClient

  class TwitterClient(val user: TwitterUserProfile) extends Tweeter {
    def tweet(msg: String) = {
      val twt = new Tweet(user, msg, new Date)
      if (service.sendTweet(twt)) {
        localCache.saveTweet(twt)
        ui.showTweet(twt)
      }
    }
  }
}

以下のように依存関係を抽象フィールドとして宣言するよりも、これはどのように優れていますか?

trait TwitterClient(val user: TwitterUserProfile) extends Tweeter {
  //abstract fields instead of cake pattern self types
  val service: TwitterService
  val localCache: TwitterLocalCache
  val ui: TwitterClientUI

  def tweet(msg: String) = {
    val twt = new Tweet(user, msg, new Date)
    if (service.sendTweet(twt)) {
      localCache.saveTweet(twt)
      ui.showTweet(twt)
    }
  }
}

DIが実際に発生するインスタンス化時間を見ると(私が理解しているように)、ケーキの利点を理解するのに苦労しています。特に、ケーキ宣言(囲み特性)のために必要な追加のキーボード入力を考慮すると

    //Please note, I have stripped out some implementation details from the 
    //referenced example to clarify the injection of implemented dependencies

    //Cake dependencies injected:
    trait TextClient
        extends TwitterClientComponent
        with TwitterClientUIComponent
        with TwitterLocalCacheComponent
        with TwitterServiceComponent {


      // Dependency from TwitterClientComponent:
      val client = new TwitterClient

      // Dependency from TwitterClientUIComponent:
      val ui = new TwitterClientUI

      // Dependency from TwitterLocalCacheComponent:
      val localCache = new TwitterLocalCache 

      // Dependency from TwitterServiceComponent
      val service = new TwitterService
    }

ここでも抽象フィールドを使用しますが、多かれ少なかれ同じです!:

trait TextClient {
          //first of all no need to mixin the components

          // Dependency on TwitterClient:
          val client = new TwitterClient

          // Dependency on TwitterClientUI:
          val ui = new TwitterClientUI

          // Dependency on TwitterLocalCache:
          val localCache = new TwitterLocalCache 

          // Dependency on TwitterService
          val service = new TwitterService
        }

私はケーキの優位性について何かを見逃しているに違いない! ただし、現時点では、他の方法 (コンストラクター、抽象フィールド) で依存関係を宣言することで何が提供されるのかわかりません。

4

4 に答える 4

8

セルフタイプアノテーション付きの特性は、フィールドインジェクション付きの古いファションのBeanよりもはるかに構成可能です。これはおそらく2番目のスニペットで念頭に置いていたものです。

この特性をどのようにインスタンス化するかを見てみましょう。

val productionTwitter = new TwitterClientComponent with TwitterUI with FSTwitterCache with TwitterConnection

この特性をテストする必要がある場合は、おそらく次のように記述します。

val testTwitter = new TwitterClientComponent with TwitterUI with FSTwitterCache with MockConnection

うーん、少しDRY違反。改善しましょう。

trait TwitterSetup extends TwitterClientComponent with TwitterUI with FSTwitterCache
val productionTwitter = new TwitterSetup with TwitterConnection
val testTwitter = new TwitterSetup with MockConnection

さらに、コンポーネント内のサービス間に依存関係がある場合(たとえば、UIがTwitterServiceに依存している場合)、それらはコンパイラーによって自動的に解決されます。

于 2011-08-10T19:49:29.537 に答える
7

TwitterServiceを使用するとどうなるか考えてみてくださいTwitterLocalCache。宣言した にアクセスできないため、TwitterService自己型付けするとはるかに簡単になります。Cake パターン (および自己型付け) を使用すると、はるかに普遍的で柔軟な方法で注入できます (もちろん、とりわけ)。TwitterLocalCacheTwitterServiceval localCache

于 2011-08-10T14:30:13.473 に答える
1

この例では、大きな違いはないと思います。自己型は、特性がいくつかの抽象値を宣言する場合に、より明確になる可能性があります。

trait ThreadPool {
  val minThreads: Int
  val maxThreads: Int
}

次に、いくつかの抽象値に依存する代わりに、ThreadPool への依存を宣言するだけです。私にとっての自己型 (Cake パターンで使用されている) は、複数の抽象メンバーを一度に宣言し、それらに便利な名前を付ける方法にすぎません。

于 2013-10-10T21:35:18.343 に答える
1

実際の配線がどのように機能するか確信が持てなかったので、リンク先のブログ エントリの簡単な例を、提案されたような抽象プロパティを使用して適応させました。

// =======================  
// service interfaces  
trait OnOffDevice {  
  def on: Unit  
  def off: Unit  
}  
trait SensorDevice {  
  def isCoffeePresent: Boolean  
}  

// =======================  
// service implementations  
class Heater extends OnOffDevice {  
  def on = println("heater.on")  
  def off = println("heater.off")  
}  
class PotSensor extends SensorDevice {  
  def isCoffeePresent = true  
}  

// =======================  
// service declaring two dependencies that it wants injected  
// via abstract fields
abstract class Warmer() {
  val sensor: SensorDevice   
  val onOff: OnOffDevice  

  def trigger = {  
    if (sensor.isCoffeePresent) onOff.on  
    else onOff.off  
  }  
}  

trait PotSensorMixin {
    val sensor = new PotSensor
}

trait HeaterMixin {
    val onOff = new Heater  
}

 val warmer = new Warmer with PotSensorMixin with HeaterMixin
 warmer.trigger 

この単純なケースでは機能します(したがって、提案する手法は実際に使用できます)。

ただし、同じブログには、同じ結果を達成するための少なくとも他の 3 つの方法が示されています。選択は主に読みやすさと個人的な好みによるものだと思います。私見を提案する手法の場合、 Warmer クラスは、依存関係を注入するという意図を十分に伝えません。また、依存関係を結び付けるために、さらに 2 つのトレイト (PotSensorMixin と HeaterMixin) を作成する必要がありましたが、それを行うためのより良い方法が頭に浮かんだかもしれません。

于 2011-08-10T15:14:37.513 に答える