Можно ли использовать Cake Pattern для зависимостей не-синглтонного стиля? - PullRequest
4 голосов
/ 04 марта 2011

Большинство примеров 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 ]

6 голосов
/ 05 марта 2011

Как вы можете видеть из ответов (особенно Даниэля, но также и ваших собственных), это возможно, но это не выглядит элегантно. Сложность возникает из-за того, что когда вы используете шаблон 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, я бы посоветовал вам использовать более простое решение. Я писал об этом здесь .

2 голосов
/ 04 марта 2011

Поскольку каждому «клиенту» может потребоваться отдельная реализация, вы можете просто параметризовать сервис.

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)
1 голос
/ 04 марта 2011

Это компилируется и работает как положено, но оставляет желать лучшего:

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