簡體   English   中英

Cake Pattern可以用於非單例樣式依賴嗎?

[英]Can the Cake Pattern be used for non-singleton style dependencies?

我遇到的蛋糕模式的大多數示例似乎都將依賴關系視為單例類型服務; 在組件的最終組裝中,每種類型只有一個實例。 在使用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 = ...
  }
}

貿易和公司服務,每個服務都依賴於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)
  }
}

依賴貿易和公司服務的主要應用程序組件:

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))
      })
  }
}

是否可以連接App以使其TradeService使用指向一個地址的HttpService,並且其CompanySerivce使用指向另一個地址的不同HttpService實例?

正如你從答案中看到的那樣(尤其是丹尼爾的,也是你自己的),這是可能的,但它看起來並不優雅。 出現這種困難是因為當您使用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,我會建議你使用更簡單的解決方案。 我在這里寫過博客。

由於每個“客戶端”可能需要不同的實現,您可以只參數化服務。

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)

這會按預期編譯並運行,但它還有很多不足之處:

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))
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM