Стратегия внедрения зависимостей Йонаса Бонера кажется ограниченной - но, может быть, я ее не понимаю - PullRequest
6 голосов
/ 02 ноября 2010

Я читал эту статью несколько раз:

http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di.html

Я думаю, что понял. Однако есть кое-что, что я не совсем понимаю.

Глядя на его пример UserService, я вижу, что он настроил UserRepositoryComponent для инкапсуляции UserRepository. Но я не понимаю, почему UserRepositoryComponent играет две роли: он инкапсулирует UserRepository, а также предлагает ссылку на объект UserRepository.

Я пытаюсь представить, как бы я использовал этот шаблон, если бы я хотел создать службу, которая зависит от двух экземпляров UserRepository. Возможно, задача новой службы - копировать пользователей из «исходного» UserRepository в «целевой» UserRepository. Итак, я представляю что-то вроде этого:

trait CopyUserServiceComponent {
  val source: UserRepositoryComponent
  val destination: UserRepositoryComponent
  class CopyUserServiceComponent { 
    ... 
  }
}

Но это отличается от оригинального шаблона. В этом случае я определяю зависимости в самом компоненте, а не наследую их от какого-то другого компонента. Но мне кажется, что это правильный путь: компоненты должны объявлять свои зависимости, а не экземпляры включенных ими служб.

Что мне здесь не хватает?

Ответы [ 2 ]

4 голосов
/ 03 ноября 2010

В этом случае я определяю зависимости в самом компоненте, а не наследую их от какого-то другого компонента.

Шаблон торт не использует наследование для объявления зависимостей.Вы видели какое-либо «расширение» в UserServiceComponent?

Но мне кажется, что это правильный путь: компоненты должны объявлять свои зависимости, а не экземпляры включенных ими служб.

Но это именно то, что делает шаблон торта: объявлять зависимости!Возможно, если бы пример содержал def userRepositoryFactory = new UserRepository вместо val userRepository = new UserRepository, это было бы более понятно?

Итак, давайте вернемся к вашему примеру:

trait CopyUserServiceComponent {
  val source: UserRepositoryComponent
  val destination: UserRepositoryComponent
  class CopyUserServiceComponent { 
    ... 
  }
}

Давайте посмотрим, что мы не может сделать с этим:

trait CopyUserServiceComponent {
  // The module will need to see my internals!
  private val source: UserRepositoryComponent
  private val destination: UserRepositoryComponent
  class CopyUserServiceComponent { 
    ... 
  }
}

trait CopyBigUserServiceComponent extends CopyServiceComponent {
  // Any change in implementation will have to be reflected in the module!
  val tmp: UserRepositoryComponent
  ...
}

С другой стороны ...

trait UserRepositoryComponent {
  val userRepositoryFactory: () => UserRepository

  class UserRepository {
    ...
  }
} 

trait CopyUserServiceComponent {
  self: UserRepositoryComponent =>
  // No problem here
  private val source: UserRepository = userRepositoryFactory()
  private val destination: UserRepository = userRepositoryFactory()
  class CopyUserServiceComponent { 
    ... 
  }
}

trait CopyBigUserServiceComponent extends CopyServiceComponent {
  self: UserRepositoryComponent =>
  // No problem here either
  val tmp: : UserRepository = userRepositoryFactory()
  ...
}

РЕДАКТИРОВАТЬ

В дополнение к ответу давайте рассмотрим две разные потребности:

  • Мне нужно много экземпляров UserRepository.

В этом случае вы применяете шаблон неправильноуровень.В примере с Джонасом UserRepository находится на уровне фабричного синглтона.

Таким образом, в этом случае вы бы не делали UserRepository и UserRepositoryComponent, но, скажем, UserRepositoryFactoryи UserRepositoryFactoryComponent.

  • Мне нужно ровно два синглтона UserRepository.

В этом случае просто сделайте что-то вроде этого:

trait UserRepositoryComponent {
  val sourceUserService: UserService
  val destinationUserService: UserService

  class UserService ...
}
1 голос
/ 02 ноября 2010

Полагаю, Джонас в своей статье ссылается на широко принятую методологию построения масштабируемых приложений , называемую Композитная программная конструкция , что в нескольких словах можно объяснить следующим образом: все приложение (организованное на мета-уровне) представляет собой сборку, построенную из независимых компонентов, которые, в свою очередь, представляют собой композиции других компонентов и служб. С точки зрения составного программного обеспечения, «торт» (в данном примере объект ComponentRegistry) представляет собой сборку компонентов («UserServiceComponent» и «UserRepositoryComponent») и т. Д. В то время как в примерах компоненты содержат реализации служб, они могут вряд ли случится в реальном мире.

В вашем примере вам не нужно определять внутренний класс - вы можете поместить свой рабочий процесс в обычный метод:

trait CopyUserServiceComponent {
  val source: UserRepositoryComponent
  val destination: UserRepositoryComponent

  def copy = {...}
}

Он полностью соответствует исходному шаблону - важной особенностью торта является не только [только] определение зависимостей с помощью аннотаций самостоятельного типа, но и возможность абстрагироваться от конкретной реализации до того момента, когда вам нужно будет собрать сборку из компоненты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...