4

我遇到的大多数 Cake Pattern 示例似乎都将依赖关系视为单例类型的服务。在组件的最终组装中,每种类型只有一个实例。当使用 Cake Pattern 进行依赖注入时,是否可以编写具有多个特定类型实例的配置,可能以不同的方式配置?

考虑以下组件。通用 HTTP 服务:

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

Trade & Company 服务,每个都依赖于 HttpService,它们可能是不同的实例:

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 使用指向一个地址的 HttpService,而其 CompanySerivce 使用指向另一个地址的不同 HttpService 实例?

4

3 回答 3

6

正如您从答案中看到的(尤其是丹尼尔的,还有您自己的),这是可能的,但它看起来并不优雅。出现困难是因为当您使用 Cake 模式时,您将所有必需的特征混合到一个对象中(使用“with”关键字),并且您不能将一个特征多次混合到一个实例中。这就是 mixins 的工作方式,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 模式是否真的最适合您的需求,因为它实际上是一种复杂的模式,依赖注入只是其中的一部分。如果您仅将其用于 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 回答