Можно ли использовать Cake Pattern для зависимостей не-синглтонного стиля?
Большинство примеров 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, указывающий на другой адрес?
3 ответа
Как вы можете видеть из ответов (особенно Даниэля, но также и ваших собственных), это возможно, но это не выглядит элегантно. Сложность возникает потому, что когда вы используете шаблон Cake, вы смешиваете все необходимые черты в один объект (используя ключевое слово "with"), и вы не можете смешивать черту более одного раза в одном экземпляре. Вот как работают миксины, и на их основе базируется торт.
Тот факт, что вы можете заставить 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))
}
}