4

私が遭遇した Cake パターンの例のほとんどは、依存関係をシングルトン タイプのサービスと見なしているように見えます。コンポーネントの最終アセンブリには、各タイプのインスタンスが 1 つだけ存在します。依存性注入に Cake パターンを使用する場合、おそらく異なる方法で構成された、特定のタイプの複数のインスタンスを持つ構成を作成することは可能ですか?

次のコンポーネントを検討してください。汎用 HTTP サービス:

trait HttpService { def get(query:String):String }
trait HttpServiceComponent {
  val httpService:HttpService
  class HttpServiceImpl(address:String) extends HttpService {
    def get(query:String):String = ...
  }
}

それぞれ異なるインスタンスである可能性がある HttpService に依存する Trade & Company サービス:

trait TradeService { def lastTrade(symbol:String):String }
trait TradeServiceComponent {
  this:HttpServiceComponent => // Depends on HttpService
  val tradeService:TradeService
  class TradeServiceImpl extends TradeService {
    def lastTrade(symbol:String):String =
      httpService.get("symbol=" + symbol)
  }
}

trait CompanyService { def getCompanySymbols(exchange:String):String }
trait CompanyServiceComponent {
  this:HttpServiceComponent =>  // Depends on different HttpService instance
  val companyService:CompanyService
  class CompanyServiceImpl extends CompanyService {
    def getCompanySymbols(exchange:String):String =
      httpService.get("exchange=" + exchange)
  }
}

Trade & Company サービスに依存するメインのアプリ コンポーネント:

trait App { def run(exchange:String):Unit }
trait AppComponent {
  this:CompanyServiceComponent with TradeServiceComponent =>
  val app:App
  class AppImpl extends App {
    def run(exchange:String) =
      companyService.getCompanySymbols(exchange).split(",").foreach(sym => {
        val lastTrade = tradeService.lastTrade(sym)
        printf("Last trade for %s: %s".format(sym, lastTrade))
      })
  }
}

TradeService が 1 つのアドレスを指す HttpService を使用し、CompanySerivce が別のアドレスを指す別の HttpService インスタンスを使用するようにアプリを接続することは可能ですか?

4

3 に答える 3

6

回答(特にダニエルのものだけでなく、あなた自身のもの)からわかるように、それは可能ですが、エレガントに見えません。Cake パターンを使用すると、必要なすべての特性を 1 つのオブジェクトに混合し (「with」キーワードを使用)、特性を 1 つのインスタンスに複数回混合することができないため、問題が発生します。これがミックスインの仕組みであり、Cake はそれらに基づいています。

非シングルトンの依存関係を Cake に強制的に処理させることができるという事実は、それを行う必要があるという意味ではありません。そのような場合は、単純に古いコンストラクターを使用することをお勧めします。これは、自己型注釈がうまく適合しない場合です。

trait HttpService { ... }

/* HttpServiceImpl has become a top-level class now,
 * as the Cake pattern adds no more value here.
 * In addition, trait HttpServiceComponent gets deleted */
class HttpServiceImpl(address:String) extends HttpService {
  ...
}

trait TradeService { def lastTrade(symbol:String):String }
trait TradeServiceComponent {
  // The dependency on HttpService is no longer declared as self-type
  val tradeService:TradeService
  // It is declared as a constructor parameter now
  class TradeServiceImpl(httpService: HttpService) extends TradeService {
    def lastTrade(symbol:String):String =
      httpService.get("symbol=" + symbol)
  }
}

trait CompanyService { def getCompanySymbols(exchange:String):String }
trait CompanyServiceComponent {
  // Again, self-type annotation deleted
  val companyService:CompanyService
  // Again, the dependency is declared as a constructor parameter
  class CompanyServiceImpl(httpService: HttpService) extends CompanyService {
    def getCompanySymbols(exchange:String):String =
      httpService.get("exchange=" + exchange)
  }
}

App および AppComponent の特性は元の形式のままです。これで、すべてのコンポーネントを次の方法で使用できます。

object App {
  def main(args:Array[String]):Unit = {
    val appAssembly = new AppComponent 
        with TradeServiceComponent
        with CompanyServiceComponent {
      // Note, that HttpServiceComponent it neither needed nor mixed-in now
      val tradeService = new TradeServiceImpl(
        new HttpServiceImpl("http://trades-r-us.com"))
      val companyService = new CompanyServiceImpl(
        new HttpServiceImpl("http://exchange-services.com"))
      val app = new AppImpl
    }
    appAssembly.app.run(args(0))
  }
}

また、Cake パターンは実際には複雑なパターンであり、依存性注入はその一部にすぎないため、Cake パターンが本当にニーズに最適かどうかを再確認することをお勧めします。DI にのみ使用する場合は、よりシンプルなソリューションを使用することをお勧めします。私はそれについてここにブログを書きました。

于 2011-03-04T23:11:31.833 に答える
2

各「クライアント」には異なる実装が必要な場合があるため、サービスをパラメーター化するだけで済みます。

trait HttpService { def get(query:String):String }
trait HttpServiceComponent {
  def httpService(name: String):HttpService
  class HttpServiceImpl(address:String) extends HttpService {
    def get(query:String):String = ...
  }
}

このように使用するには:

trait TradeService { def lastTrade(symbol:String):String }
trait TradeServiceComponent {
  this:HttpServiceComponent => // Depends on HttpService
  val tradeService:TradeService
  class TradeServiceImpl extends TradeService {
    def lastTrade(symbol:String):String =
      httpService("TradeService").get("symbol=" + symbol)
  }
}

最終的なミックスは、次のようになります。

trait AppComponent {
  this:CompanyServiceComponent with TradeServiceComponent =>
  val httpServices = Map( "TradeService"   -> new HttpServiceImpl("http://trades-r-us.com"),
                          "CompanyService" -> new HttpServiceImpl("http://exchange-services.com"))
  def httpService(name: String) = httpServices(name)
于 2011-03-04T13:22:39.103 に答える
1

これは期待どおりにコンパイルおよび実行されますが、多くのことが望まれます。

object App {
  def main(args:Array[String]):Unit = {
    val tradeServiceAssembly = new TradeServiceComponent with HttpServiceComponent {
      val httpService = new HttpServiceImpl("http://trades-r-us.com")
      val tradeService = new TradeServiceImpl
    }
    val companyServiceAssembly = new CompanyServiceComponent with HttpServiceComponent {
      val httpService = new HttpServiceImpl("http://exchange-services.com")
      val companyService = new CompanyServiceImpl
    }
    val appAssembly = new AppComponent 
        with TradeServiceComponent
        with CompanyServiceComponent
        with HttpServiceComponent {
      lazy val httpService = error("Required for compilation but not used")
      val tradeService = tradeServiceAssembly.tradeService
      val companyService = companyServiceAssembly.companyService
      val app = new AppImpl
    }
    appAssembly.app.run(args(0))
  }
}
于 2011-03-04T06:18:15.787 に答える