Dependency Injection и принцип Inverse of Control в программировании используются уже давно. Я как понял DI — это термин, который Мартином Фаулером используется для обозначения того же IoC. Что же это такое и как его использовать нам, простым флэш разработчикам.
Все просто. Основная идея — при создании объекта ему даются (инжектируются) уже готовенькие объекты, в которых он нуждается (зависимости). Каждый из нас, интуитивно полагая, что это гуд, использовал Dependency Injection в той или иной мере. Вот простой код.
public class One{
public function One( two: ITwo )
{
this.two = two;
}
}
public class Two implements ITwo {
...
}
new One( new Two() );
Как видно, объекту класса One мы передаем объект с интерфейсом ITwo при создании. Перед нами простой пример так называемого Constructor Injection. Также есть Property Injection, когда класс предоставляет public свойство или сеттер, через который ожидает получить некоторый объект, в котором нуждается. Идея проста до безобразия и позволяет проще переиспользовать классы, которые друг на друга не завязаны.
Противоположный подход — создавать все dependency внутри класса, при этом классы становятся привязанными друг к другу, что усложняет внесение изменений.
Интереснее же все становится, когда на сцену выходит IoC Contaner. Под контейнером понимается некоторое пространство для объектов, который по определенным правилам эти объекты сам конфигурирует и сам подставляет dependencies куда и кому нужно. Проще говоря, некоторый фреймворк, который сам соображает (с нашими подсказками) как собрать те или иные объекты и какие зависимости им нужны.
AS3.
Речь пойдет о чисто AS3 проектах. Они обычно небольшие в отличие от Flex проектов, которые можно сравнить с Java Enterprise проектами. Если в Java существует толпа IoC фреймворков (как я понимаю самый популярный Spring Framework), то для ActionScript до недавнего времени все было как-то грустно. Конечно, AS3 язык гораздо моложе, чем Java, скажете вы.
Между тем я случайно наткнулся на Syringe, который мимикрирует со Spring, повторяя метод конфигурации через XML, но последняя версия выпущена в бородатом 2006м. Видим, что какие-то попытки перенести функциональность Spring на ActionScript3 велись, но не были поддержаны сообществом, потому что сообщество не было к этому готово. И, конечно же, потому, что большинство AS3 проектов небольшие, на одного программиста и дизайнера.
Какое-то время уже существует официальный Spring ActionScript (бывший Prana). Этот фреймворк вроде бы развивается и сейчас остановился на версии 0.8.
Favour of the month безусловно является Parsley, версия 2 которого радикально изменилась и поддерживает множество вкусных фишек. В том числе и для pure AS3 проектов.
Применение.
Как-то давно на основе Parsley 1.* я набросал себе небольшой фреймворк, который использую при создании и конфигурировании видео плееров и фото галерей.
Значит, допустим у нас есть видео плеер с архитектурой MVC. Разные проекты предусматривают разное расположение визуальных элементов и разные функции. Чтобы каждый раз не копипастить все это, делаем максимально decoupled компоненты и собираем их IoC контейнером при помощи XML конфига.
Вот пример такого конфига для одного из плееров. Видим команды контейнеру по сборке объектов. Любую можно убрать или добавить и плеер пересоберется подругому. Порой, даже не нужно перекомпилировать проект. Конечно, данный пример не использует таких мощных фишек контейнера, как инжектирование по типу, просто мой старенький этого не умеет.
<?xml version="1.0" encoding="UTF-8"?> <container> <commands> <!-- Скин плеера --> <object name="skin" class="com.qq.videoplayer.skin.PPSkin" /> <!-- Сам плеер --> <object name="player" class="ru.valyard.media.mediaplayer.VideoPlayer"> <constructor-args> <object ref="skin" /> </constructor-args> <!-- Контейнер для контролов --> <view name="controls" class="ru.valyard.upa.mvc.views.FixedContainer" /> <!-- Компонент показывающий картинку до проигрывания --> <view name="preview" class="ru.valyard.media.mediaplayer.views.controls.PreviewView" parent="controls"> <property name="widthRule">W</property> <property name="heightRule">H</property> </view> <!-- Анимашка, которая моргает во время буферизации --> <view name="buffering" class="ru.valyard.media.mediaplayer.views.controls.BufferingView" parent="controls"> <property name="xRule">W*.5</property> <property name="yRule">H*.5</property> </view> <!-- Большая кнопка Play в центре --> <view name="bigPlay" class="ru.valyard.media.mediaplayer.views.controls.BigPlayButton" parent="controls"> <property name="widthRule">W</property> <property name="heightRule">H</property> </view> <!-- Экран как видео проигралось --> <view name="stopView" class="ru.valyard.media.mediaplayer.views.controls.StopView" parent="controls"> <property name="xRule">W*.5</property> <property name="yRule">H*.5</property> </view> <!-- Верхние контролы --> <view name="dynamicpanel2" class="ru.valyard.media.mediaplayer.views.containers.DynamicContainer" parent="controls"> <property name="yRule">10</property> <property name="xRule">5</property> <property name="inactiveYRule">-70</property> </view> <view name="top" class="ru.valyard.upa.mvc.views.HorizontalContainer" parent="dynamicpanel2"> <constructor-args> <int>0</int> </constructor-args> </view> <!-- Fullscreen --> <view name="fullscreen" class="ru.valyard.media.mediaplayer.views.controls.FullscreenButtonView" parent="top"/> <!-- Share --> <view name="shareButton" class="ru.valyard.media.mediaplayer.views.controls.ShareButtonView" parent="top"/> <view name="popout" class="ru.valyard.media.mediaplayer.views.controls.PopoutView" parent="top"/> <!-- Основные контролы --> <view name="dynamicpanel" class="ru.valyard.media.mediaplayer.views.containers.DynamicContainer" parent="controls"> <property name="yRule">H-42</property> <property name="inactiveYRule">H</property> </view> <view name="panel" class="ru.valyard.upa.mvc.views.HorizontalContainer" parent="dynamicpanel"> <constructor-args> <int>2</int> </constructor-args> </view> <!-- Watermark --> <view name="watermark" class="ru.valyard.media.mediaplayer.views.controls.SymbolView" parent="dynamicpanel"> <constructor-args> <string>Watermark</string> </constructor-args> <property name="xRule">W</property> </view> <!-- Play/pause buttons --> <view name="playpause" class="ru.valyard.media.mediaplayer.views.controls.PlayPauseView" parent="panel"/> <view name="prev" class="ru.valyard.media.mediaplayer.views.controls.PreviousView" parent="panel"/> <view name="next" class="ru.valyard.media.mediaplayer.views.controls.NextView" parent="panel"/> <view name="line" class="ru.valyard.media.mediaplayer.views.controls.LineView" parent="panel"> <property name="widthRule">W-playpause.width-sound.width-next.width-prev.width</property> <property name="leftSpace">1</property> <property name="topSpace">12</property> <property name="rightSpace">4</property> <property name="scrollerDist">6</property> </view> <!-- Sound on/off button --> <view name="sound" class="ru.valyard.media.mediaplayer.views.controls.HorizontalSoundView" parent="panel"/> <!-- Share window --> <array name="sizesArray"> <string>640x480</string> <string>480x360</string> <string>320x240</string> </array> <view name="shareView" class="ru.valyard.media.mediaplayer.views.controls.ShareView" parent="controls"> <property name="xRule">W*.5</property> <property name="yRule">H*.5</property> <property name="sizes" ref="sizesArray"/> </view> <!-- Ad --> <view name="ad" class="ru.valyard.media.mediaplayer.views.thirdparty.TremorMediaAdsView" parent="controls"> <property name="widthRule">W</property> <property name="heightRule">H</property> <property name="id">123</property> </view> <module class="com.qq.videoplayer.modules.PlayCount" name="playcount"> <constructor-args> <string>http://qq.com/qq.php</string> </constructor-args> </module> <!-- Debug actions --> <action class="ru.valyard.media.mediaplayer.actions.TestAction" name="*" priority="3" /> <!-- XML loader class --> <object name="loader" class="ru.valyard.media.mediaplayer.loaders.PlaylistXMLLoader" /> <method name="registerLoader"> <object ref="loader" /> </method> <!-- Embed HTML template --> <property name="embedHTMLTemplate"> <![CDATA[<object width="%W%" height="%H%"><param name="movie" value="http://www.qq.com/swfs/embed.swf?id=%ID%"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.qq.com/swfs/embed.swf?id=%ID%" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="%W%" height="%H%"></embed></object>]]> </property> <!-- --> <property name="pageURLTemplate"> <![CDATA[http://www.qq.com/video/%PAGE%]]> </property> <!-- --> <property name="dataURLTemplate"> <![CDATA[http://www.qq.com/xml/%DATA%]]> </property> <property name="mediaURLTemplate"> <![CDATA[http://www.qq.com/videos_final/%URL%]]> </property> </object> </commands> </container>
С первого взгляда все сложно, хотя такая конфигурация успела сохранить мне кучу времени при создании однотипных модульных видео плееров немного отличающихся по скину и функциональности.
Плюсы.
Про конфигурирование плеера я уже сказал. Это несомненно плюс. Этот подход позволяет максимально разделить используемые объекты друг от друга и легко переиспользовать их в других проектах. В осноном, избавляет от неободимости писать кучу конфигурационного кода. Конечно, приведенный здесь конфиг не является идеальным примером. СтОит почитать хелп Parsley, который имеет куда более продвинутые способы конфигурирования.
Минусы.
Есть какая-то граница, при переходе которой конфигурирование контейнера превращается в более time consuming процесс, чем если бы я просто сел и руками все написал. Конечно, IoC позволяет создать задел на будущее для возможных исправление и переработок, но как показывает практика, часто нужно просто быстренько наклепать код и избавиться от него. В небольших проектах смысла использовать особо нет, если не имеет место сборка маленьких reusable компонентиков.
