Як зробити ін’єкцію залежності з шаблоном Cake без жорсткого кодування?


74

Я щойно прочитав і сподобався статті про шаблон торта . Однак, на мій погляд, однією з ключових причин використання ін’єкції залежностей є те, що ви можете змінювати компоненти, що використовуються як файлом XML, так і аргументами командного рядка.

Як з цим аспектом DI обробляється шаблон Cake? Усі приклади, які я бачив, стосуються статичного змішування ознак.

Відповіді:


56

Оскільки змішування ознак здійснюється статично в Scala, якщо ви хочете змінити ознаки, змішані з об’єктом, створіть різні об’єкти на основі певної умови.

Давайте візьмемо приклад канонічного торта. Ваші модулі визначаються як риси, а ваш додаток побудований як простий Об'єкт із змішаною функціональністю

val application =
    new Object
extends Communications
   with Parsing
   with Persistence
   with Logging
   with ProductionDataSource
application.startup

Тепер усі ці модулі мають приємні декларації самотипу, які визначають їх міжмодульні залежності, тому цей рядок компілюється, лише якщо всі ваші міжмодульні залежності існують, є унікальними та добре набраними. Зокрема, модуль Persistence має власний тип, який говорить про те, що все, що реалізує Persistence, має також реалізовувати DataSource, абстрактну властивість модуля. Оскільки ProductionDataSource успадковує DataSource, все чудово, і ця лінія побудови додатків компілюється.

Але що, якщо ви хочете використовувати інший DataSource, вказуючи на якусь локальну базу даних для цілей тестування? Далі припустимо, що ви не можете просто повторно використовувати ProductionDataSource з різними параметрами конфігурації, завантаженими з файлу деяких властивостей. Що б ви зробили в такому випадку, це визначили нову ознаку TestDataSource, яка розширює DataSource, і замішали її. Ви навіть можете робити це динамічно на основі прапора командного рядка.

val application = if (test)
  new Object
    extends Communications
      with Parsing
      with Persistence
      with Logging
      with TestDataSource
else
  new Object
    extends Communications
      with Parsing
      with Persistence
      with Logging
      with ProductionDataSource

application.startup

Тепер це виглядає дещо детальнішим, ніж ми хотіли б, особливо якщо вашій програмі потрібно змінювати свою конструкцію за кількома осями. Позитивною стороною є те, що зазвичай у вас є лише одна частина умовної логіки побудови, така як у додатку (або в гіршому випадку один раз за ідентифікований життєвий цикл компонента), так що принаймні біль зведений до мінімуму та відгороджений від решти вашої логіки.


Чистий канонічний зразок торта, хоча, я думаю, ОП шукав спосіб додати рису до вже створеного об’єкта. Проблема в тому, що це можливо лише під час будівництва.
Кевін Райт,

10
@Dave: чи справді ви припускаєте, що ваша заява "якщо" готова до виробництва та щось, що ви б розгорнули на корпоративному програмному забезпеченні? Це справді поганий код IMHO, оскільки він не може відокремити проблему розгортання (яку базу даних) від проблеми коду (як складається додаток). Пошук бази даних - це те, що слід зробити, скажімо, у дереві JNDI; це ніколи не повинно мати жорсткого кодування, оскільки для внесення змін потрібно повторне розгортання.
Ant Kutschera

6
Я б не робив цього так у виробничому коді, якби не було альтернативи. Це була просто найкраща відповідь, яку я знаю, на оригінальне запитання, що насправді означало необхідність змінити спосіб складання програми під час виконання. Якщо сказати "ніколи цього не потрібно робити, оскільки все можна зробити, налаштувавши компоненти окремо", виникає питання.
Дейв Гріффіт,

1
Якщо вас турбує аспект програмної інженерії шаблону тортів, речення "test" можна просто вбудувати у ваші модульні тести, тоді як інше / виробниче положення залишатиметься в основному додатку.
jhclark

2
@jhclark виробництво, стадія, розробка, тест: як обробляти ці сценарії, коли ви можете мати справу з кількома базами даних залежно від умов виконання? Наведене вище посилання на дерево JNDI є хорошим, але я не впевнений, що воно охоплює всі основи, тоді як реалізація торта може охопити все, лише набагато більшою кількістю зразків, і, як згадує Дейв у своїй відповіді, "біль" ;-)
virtualeyes

29

Scala - це також мова сценаріїв. Отже, ваша конфігурація XML може бути сценарієм Scala. Це типобезпека та не зовсім інша мова.

Просто подивіться на запуск:

scala -cp first.jar:second.jar startupScript.scala

не так сильно відрізняється від:

java -cp first.jar:second.jar com.example.MyMainClass context.xml

Ви завжди можете використовувати DI, але у вас є ще один інструмент.


5

Коротка відповідь полягає в тому, що Scala наразі не має вбудованої підтримки динамічних міксів.

Я працюю над плагіном autoproxy, щоб підтримати це, хоча в даний час він утримується до випуску 2.9, коли компілятор отримає нові функції, що полегшують роботу.

Тим часом найкращий спосіб досягти майже точно тієї ж функціональності - це реалізація вашої динамічно доданої поведінки як класу обгортки, а потім додавання неявного перетворення назад до обгорнутого члена.


чи десь пояснені ці нові функції?
pedrofurla 03.03.11

@pedrofurla - У вихідному коді :) Компілятор 2.9 має кращі способи відкочування після того, як одиниця вийшла з ладу на фазі друку, це значною мірою було реалізовано для використання компілятором презентацій (як використовується eclipse та ensime). Це актуально для мене, оскільки автопроксі-плагін використовує техніку набору тексту за два проходи, один раз для генерації інформації про тип, необхідну для методів делегування, і знову для одиниць, які не ввели текст перед синтезом делегатів. Я стикався з проблемами з невідповідностями, яких не було в таблиці символів після першого невдалого проходження.
Кевін Райт,

1
@pedrofurla - Не те, що будь-що з цього є важливим для загального програмування Scala, лише для людей, які роблять певний клас обману з плагінами компілятора.
Кевін Райт,

3

Поки плагін AutoProxy не стане доступним, одним із способів досягти ефекту є використання делегування:

trait Module {
  def foo: Int
}

trait DelegatedModule extends Module {
  var delegate: Module = _
  def foo = delegate.foo
}

class Impl extends Module {
  def foo = 1
}

// later
val composed: Module with ... with ... = new DelegatedModule with ... with ...
composed.delegate = choose() // choose is linear in the number of `Module` implementations

Але будьте обережні, недоліком цього є те, що він більш багатослівний, і ви повинні бути обережними щодо порядку ініціалізації, якщо ви використовуєте vars всередині ознаки. Іншим недоліком є ​​те, що якщо вгорі є типи, що залежать від шляху Module, ви не зможете використовувати делегування так легко.

Але якщо існує велика кількість різних реалізацій, які можна варіювати, це, ймовірно, коштуватиме вам менше коду, ніж перелік випадків з усіма можливими комбінаціями.


новий делегований модуль? Ви можете заявити про "нові" риси? Що означає підкреслення в цьому конкретному контексті? Я так розгубився.
Ryan The Leach

0

У Lift є щось вбудоване в ці рядки. Це в основному в масштабному коді, але у вас є певний контроль за часом виконання. http://www.assembla.com/wiki/show/liftweb/Dependency_Injection


Хм, мені взагалі подобається і Lift як фреймворк, і Lift як бібліотека. Але в цьому конкретному випадку це не дуже хороша відповідь, як я бачу. Основна причина полягає в тому, що коректність програми насправді не перевіряється компілятором. Бібліотека очікує, що будь-який виклик DI може провалитися, навіть результатом кожного виклику DI є "Option" -al.
ВасильНовіков
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.