11

Cake パターンを使用して Scala に依存性注入を実装しようとしていますが、依存性衝突が発生しています。このような依存関係を持つ詳細な例が見つからなかったため、ここに私の問題があります:

次の特性があるとします (2 つの実装があります)。

trait HttpClient {
  def get(url: String)
}

class DefaultHttpClient1 extends HttpClient {
  def get(url: String) = ???
}

class DefaultHttpClient2 extends HttpClient {
  def get(url: String) = ???
}

そして、次の 2 つのケーキ パターン モジュール (この例では、どちらも機能を our に依存する API ですHttpClient):

trait FooApiModule {
  def httpClient: HttpClient        // dependency
  lazy val fooApi = new FooApi()    // providing the module's service

  class FooApi {
    def foo(url: String): String = {
      val res = httpClient.get(url)
      // ... something foo specific
      ???
    }
  }
}

trait BarApiModule {
  def httpClient: HttpClient        // dependency
  lazy val barApi = new BarApi()    // providing the module's service

  class BarApi {
    def bar(url: String): String = {
      val res = httpClient.get(url)
      // ... something bar specific
      ???
    }
  }
}

両方のモジュールを使用する最終的なアプリを作成するときは、両方httpClientのモジュールに依存関係を提供する必要があります。しかし、モジュールごとに異なる実装を提供したい場合はどうでしょうか? それとも、別の方法で構成された依存関係の別のインスタンスを提供するだけExecutionContextですか?

object MyApp extends FooApiModule with BarApiModule {
  // the same dependency supplied to both modules
  val httpClient = new DefaultHttpClient1()

  def run() = {
    val r1 = fooApi.foo("http://...")
    val r2 = barApi.bar("http://...")
    // ...
  }
}

モジュールごとに依存関係に異なる名前を付けて、モジュール名の前に付けることができますが、それは面倒で洗練されていません。また、モジュールを自分で完全に制御できない場合は機能しません。

何か案は?私はケーキのパターンを誤解していますか?

4

5 に答える 5

8

パターンを正しく取得し、その重要な制限を発見しました。2 つのモジュールが何らかのオブジェクト (HttpClient など) に依存し、たまたま同じ名前 (httpClient など) で宣言された場合、ゲームオーバーです。1 つの Cake 内でそれらを個別に構成することはありません。ダニエルのアドバイスのようにケーキを2つ用意するか、可能であればモジュールのソースを変更してください(Tomer Gabelが示唆しているように)。

これらのソリューションにはそれぞれ問題があります。

Cake を 2 つ持つこと (Daniel のアドバイス) は、いくつかの共通の依存関係を必要としない限りは良さそうです。

一部の依存関係の名前を変更すると (可能であれば)、それらを使用するすべてのコードを調整する必要があります。

したがって、一部の人々 (私を含む) は、単純な古いコンストラクターを使用して Cake を完全に避けるなど、これらの問題に影響されないソリューションを好みます。あなたがそれを測定した場合、それらはコードにあまり肥大化を追加せず (Cake はすでにかなり冗長です)、はるかに柔軟です。

于 2013-12-09T23:29:58.527 に答える
3

「あなたは間違っています」(TM)。Spring、Guice、または任意の IoC コンテナーでまったく同じ問題が発生します。型を名前 (またはシンボル) として扱っています。「fooApi との通信に適した HTTP クライアントを提供してください」ではなく、「HTTP クライアントを提供してください」と言っているのです。

つまり、すべて という名前の複数の HTTP クライアントがhttpClientあり、異なるインスタンスを区別することはできません。これは、参照を修飾する方法なしで @Autowired HttpClient を取得するようなものです (Spring の場合、通常は外部配線による Bean ID によって)。

ケーキ パターンでは、これを解決する 1 つの方法は、その区別を別の名前で修飾FooApiModuleするdef http10HttpClient: HttpClientことBarApiModuleですdef connectionPooledHttpClient: HttpClient。異なるモジュールを「埋める」場合、異なる名前は両方とも 2 つの異なるインスタンスを参照しますが、2 つのモジュールが依存関係に課す制約も示しています。

別の方法 (私の意見ではクリーンではありませんが実行可能) は、単にモジュール固有の名前付き依存関係を要求することです。つまりdef fooHttpClient: HttpClient、モジュールを混在させる人に明示的な外部配線を強制するだけです。

于 2013-12-09T15:01:45.800 に答える
1

既知の「ロボットの脚」の問題のようです。ロボットの 2 つの脚を作成する必要がありますが、2 つの異なる足を供給する必要があります。

ケーキパターンを使用して、共通の依存関係と個別の依存関係の両方を持たせるにはどうすればよいですか?

持ってみましょうL1 <- A, B1; L2 <- A, B2. そして、あなたはしたいですMain <- L1, L2, A

個別の依存関係を持つには、共通の依存関係でパラメーター化された小さなケーキの 2 つのインスタンスが必要です。

trait LegCommon { def a:A}
trait Bdep { def b:B }
class L(val common:LegCommon) extends Bdep { 
  import common._
  // declarations of Leg. Have both A and B.
}
trait B1module extends Bdep {
  val b = new B1
}
trait B2module extends Bdep {
  def b = new B2
}

ではMain、ケーキと 2 本の脚に共通部分があります。

trait Main extends LegCommon {
  val l1 = new L(this) with B1module
  val l2 = new L(this) with B2module
  val a = new A
}
于 2013-12-10T06:29:43.413 に答える
0

最終的なアプリは次のようになります。

object MyApp {
  val fooApi = new FooApiModule {
    val httpClient = new DefaultHttpClient1()
  }.fooApi
  val barApi = new BarApiModule {
     val httpClient = new DefaultHttpClient2()
  }.barApi
  ...

 def run() = {
  val r1 = fooApi.foo("http://...")
  val r2 = barApi.bar("http://...")
  // ...
 }
}

それはうまくいくはずです。(このブログ投稿から適応: http://www.cakesolutions.net/teamblogs/2011/12/19/cake-pattern-in-depth/ )

于 2013-12-11T06:59:06.207 に答える