Update: Чем дальше я читал статью Фаулера об инверсии зависимостей (dependency injection), тем яснее видел, сколь наивна моя нижеприведенная попытка эту инверсию реализовать. Однако оставляю все как было напечатано. Кстати, на эту же тему есть отличная статья на русском: "Инверсия зависимостей при проектировании Объектно-Ориентированных систем". Долой костыли!
Возвращаясь к начатому Клишиным разговору о впрыскивании зависимостей в классы. Если коротко -- чтобы использующие друг друга классы меньше друг от друга зависели, то есть чтобы их действительно можно было безболезненно переносить из проекта в проект, придуман механизм наружного "вкалывания" в них этих зависимостей. Сначал постмотрим, как без этого; так, если написать в своем классе:
static private var app: App = App.getInstance(); static private var dc:Function = DelegateExt.create; static private var debug:Function = Debugger.getInstance().debug;
то даже в таком простом случае получим залипший именно на данной конфигурации класс, предполагающий, что для делегирования в нем всегда будет использоваться метод "DelegateExt.create", для трассировки -- Debugger.getInstance().debug, а класс основного приложения всегда называется App. Для внедрения такого класса в другую структуру придется преписывать его код, а это нехорошо.
DelegateExt.create
Debugger.getInstance().debug
Чтобы решить эту проблему и снизить зависимость классов друг от друга, и придуман механизм "Dependency Injection", т.е. -- "вкалывание зависимостей". Суть его в том, что зависимые от внешних изменяющихся классов пременные приобретают свои зависимые значения не внутри самого класса при объявлении, а задаются снаружи -- так мы уходим от необходимости переписывать класс при его переносе в другие проекты. Для впрыскивания зависмостей существует несколько методов, описанных по приведенной выше ссылке. Среди них -- метод сеттеров, когда зависимые свойства задаются через функции-сеттеры -- мне он понравился воей простотой.
Пытаясь применить механизм вкалывания зависмостей через сеттеры на своем проекте, я пришел к тому, что мои сеттеры и геттеры нагло разрастаются. Поэтому я вывел для себя комбинированный метод впрыскивания заависмостей. Пример:
Сам класс Ummmmm, использукющий вколотые зависимости:
class Ummmmm extends UIObject { /* * Dependency Injection Declarations */ private var app : Object; private var debug : Function; private var dc : Function; private var transformee : MovieClip; /* * Dependency Setter */ public function setDependencies(obj : Object) { for (var i:String in obj){ this[i] = obj[i]; } }
Пример использования:
ummmmm = Ummmmm.getInstance(); ummmmm.setDependencies({app:getInstance(), dc: DelegateExt.create, debug: Debugger.getInstance().debug});
Работает. Но должны быть в такой простоте и подводные камни.. предлагаю обсудить!
Я бы предпочел декларировать определенный интерфейс к классам, типа App, DelegateExt... так как это классы более общие, нежели Ummmm...и соответственно меняться они будут реже.
А если требуется внедрить в проект чужой класс - можно написать wrapp-ер с нужным интерфейсом для него. Но никак не setDependencies...Почему? Да потому что это еще один потенциальный источник ошибок.
Соответствие же классов интерфейсам - проверяется в момент компиляции...и ошибок гораздо легче избежать
private var app: App = App.getInstance(); private var dc:Function = DelegateExt.create; private var debug:Function = Debugger.getInstance().debug; - разве эти конструкции рабочие?
если говорить о ситуации в целом, то чем больше мы пытаемся избежать зависимостей, тем более общим становится решение, тем меньше возможностей отловить ошибки времени компиляции: было: private var app : App стало: private var app : Object;
Да, это наследие AS2... Таким образом создаются не члены класса а члены прототипа. От этого надо уходить, так как это абсолютно не явная конструкция... Например: создаете 1000 инстансов Ummm, а App.getInstance, Debugger.getInstance и т.д. - вызываются единожды...
И еще пример: private var _childs = [];
...а потом удивляетесь что таким образом объявленный масив ведет себя как
static private var _childs = [];
На самом деле он сидит в Umm.prototype._childs = []. Вот так то...
// Подготовка --------------------------------------------
// Декларация интерфейса interface IDelegator { function create(); }
// Собственно то, что вы называете инъекцией (родная версия) class MyDelegator implements IDelegator { function create() { ... } }
// Если вдруг нужно использовать SuperDelegator class SuperDelegatorWrapper implements IDelegator { var d:SuperDelegator; function create() { d.CreateDelegated(...); } }
// Использование ----------------------------------------- // Сосбтвенно самое интересное, класс Ummm... class Ummm { var d:IDelegator; function Ummm() { d = new MyDelegator(); } function yo() { d.create(); } }
// (...не спешите...читайте далее...)
// Подготовка 2 ------------------------------------------ // А если нужна пакетная смена классов...то делаем типа:
class AppRuntimeConfig { static function get Delegator():IDelegator { return new SuperDelegatorWrapper(); } // или к примеру: static dbg:IDebugger = new MyDebug(); static function get Debugger():IDebugger { return dbg; } }
// Использование 2 --------------------------------------- class Ummm { var d:IDelegator; function Ummm() { d = AppRuntimeConfig.Delegator; } function yo() { d.create(); } }
Классы, интерфейсы, паттерны проектирования...вот все что нам нужно :) Меньше ошибок и никаких инъекций! :)
Хм. Как бы это сказать. Ну а чем готовые IoC контейнеры не устраивают? (а их уже 2 штуки есть)
Не Spring, конечно, но все же.
Готовые IoC я не использую...предпочитаю самописный код :)
И советовать по поводу того - стоит или не стоит их использовать - ничего не хочу...каждый кодер сам решает что ему нужно...
У каждого свой стиль :) Кунг-Фу там или стиль дракона... :)
Iv, прости подлеца, там static'и должны быть (и были, я их неподумавши снес). Уже вернул.
Василий, Dependency Injection (в русском его чаше называют паттерном "Инверсия Зависимостей")-- это распространенный паттерн (с руки Фаулера, как я понимаю).
А здесь я пытаюсь подступиться к нему с минимальными усилиями (для такого чайника в реальных паттернах как я %-)
Спасибо за пищу для ума!
Да, и насчет интерфейсов сказано верно. В правильном DI (не в моем примере ;) именно они и используются для точного впрыскивания.
Рост, спсибо за линк!
Познавательно...кстати IoC - оказывается имеет две имплементации по Фаулеру в зависимости от того - кто инициирует применение нужного класса:
Dependency Injection и ServiceLocator
1) В этой статье ты раскрыл только первый тип - Dependency Injection 1) А в предыдущем посте, Подготовка 2 и использование 2 это - ServiceLocator называется по Фаулеру...спасибо, не знал 8)
А какой метод выбрать - это решает все же кодер, как ему нравится...
Можно прочитать раздел: Deciding which option to use из той же страницы
Рост, вопрос немного не в тему. Просто интересно, вот ты унаследовался от UIObject, а что оно тебе дает? Тот же MovieClip, только код плодит (пишу я и думаю, что конечно же заблуждаюсь).
Мне не раз уже пригодился метод UIObject.invalidate()
А именно здесь он UIObject ничего не дает, случайно попал из кода.
>> У каждого свой стиль :) Кунг-Фу там или стиль дракона... :)
У русских флэшеров в 80% случаев почему-то стиль HandsOnBicyclesInvention. Хотя тот же Spring отлично показал себя в приложениях масштаба Red5 -- нет же, зачем нам смотреть, что и как там делают другие -- времени-то на исправления вагон...
...Да, именно "велосипеды" :) А что поделаешь если подход не нравится?