понедельник, января 09, 2006

Mixins: что это такое, с чем это едят? Хорошо это или плохо?

По поводу возникшей в конференции ruFlash дискуссии насчет множественного наследования в компонентах v2 от Macromedia всплыл вопрос насчет так называемых mixins, которые присутствуют в аржитектуре этих компонент. Действительно, вопрос о mixins, насколько я знаю, в русскоязычных обзорах не поднимался и я попробую этот момент немного осветить. Для начала сам корень вопроса:

Неоторые краем уха слышали, что в макромедийном фрэймворке, целиком написанном на ActionScript 2, проворачивается такая штука, как множественное наследование. А, как мы все знаем, ActionScript 2.0 - это язык программирования с полноценным объектно-ориентированным синтаксисом. Я не зря говорю про синтаксис, ибо только им дело и ограничевается. Тут даже не нужен никакой Нео чтобы открыть полог матрицы, не очень ловко прикрывающей торчащий из-под AS2 несвежо попахивающий AS1. Да, друзья мои, все мы хорошо знаем и скорбим, но AS2 компилируется в обычный AS1-байткод. А что есть такое AS1?

«ActionScript, как и его прообраз JavaScript, являются прототипированными языками. В прототипированных языках вы можете изменять определения класса на этапе исполнения и таким образом с легкостью добавлять функциональность. Вы добавляете методы другого класса в ваш класс путем достуупа к прототипу вашего класса.»

Понятное дело, что это я не сам такой умный, а привожу цитату из статьи, опубликованной на сайте Macromedia и посвященной mixins в Flex (считается, что Flex - это среда для серьезных разработчиков, ничего не знающих о тяжком прошлом Flash-разработчиков и потому для них специально объясняют всякие дикости; так или иначе, но все, что описывается в этой статье касается AS2 и Flex 1.x Components Framework, ноги которого растут напрямую из v2 components framework от Macromedia). Желающие могут сразу читать статью на английском, а для ленивых (шутка :) я продолжу тезисный обзор.

Наш друг из солнечной Индии Abdul Qabiz тоже интересовался этим вопросом и нашел в википедии немного истории происхождения термина. Приведу на русском:

«В ОО-языках mixins являются попыткой воплотить классы, отличающиеся от широкоиспользуемой практики пришедшей из Симулы (то, что используется в Smalltalk, C++, Java, C#). Впервые mixins использованы в Flavors (раннее объектно-ориентированное расширение Лиспа). Преимущество mixins в их способствовании повторному использованию кода и избегании широкоизвестных патологий, связанных с множественным наследованием, но между тем mixins имеют и собственный набор компромиссов. С mixins определение класса описывает только атрибуты и параметры, методы же определяются в других местах.»

Из этого Flavors (вкусы, привкусы, приправы) и родились mixins в программировании (как термин). А придуман он был так: в 1970-х годах в Массачусетском Технологическом Институте было популярное место встреч - Мороженое Стива (Steve's Ice Cream). Вы могли сделать собственное мороженое просто выбрав основу и различные добавки, которые назывались mixins.

Понятно, что в этих вот широкоиспользуемых ОО-языках типа Java mixins представляют тот "душок" (code that smells), который устраняется путем рефакторинга и более продуманной и изящной архитектуры с более понятным и читаемым кодом. И, стремясь к лучшим образцам (об этом в коце статьи), мы (вернее Macromedia) и с помощью AS2 вполне можем избежать этих mixins, о которых так долго уже говорим, но вы, думаю, так еще и не поняли, о чем я конкретно толкую. Хорошо, перейдем к примеру.

Наиболее употребимый пример mixins - это использование событийной модели и класса mx.events.EventDispatcher. Вы создаете собственный класс и желаете иметь в нем ту же событийную модель, что и в остальных классах от Macromedia (в рамках пакета mx). А также вы хотите чтобы ваш класс был отнаследован от класса, такой модели не имеющего. Что вы делаете? Вы пишете:


Created with Colorer-take5 Library. Type 'actionscript'

0: import mx.events.EventDispatcher;
1:
2: class Client extends Date
3: {
4: public function Client ()
5: {
6: EventDispatcher.initialize (this);
7: }
8: }

И все. А дальше в дело вступают эти самые mixins. В классе mx.events.EventDispatcher мы видим:


13: class mx.events.EventDispatcher
14: {
.............
43: static function initialize(object:Object):Void
44: {
45: if (_fEventDispatcher == undefined)
46: {
47: _fEventDispatcher = new EventDispatcher;
48: }
49: object.addEventListener = _fEventDispatcher.addEventListener;
50: object.removeEventListener = _fEventDispatcher.removeEventListener;
51: object.dispatchEvent = _fEventDispatcher.dispatchEvent;
52: object.dispatchQueue = _fEventDispatcher.dispatchQueue;
53: }
..............................
143: }
144:

Именно здесь и совершается таинство mixins. Понятно, что для того чтобы методы addEventListener и removeEventListener нормально обрабатывались нашей IDE и компилятором, необходимо в классе Client присутствовали заглушки этих методов: просто для синтаксических нужд, не более. Есть два основных способа: написать их как свойства класса типа Function (тогда не будут проверяться компилятором типы параметров) и в виде метода с реальной сигнатурой, но без всякой имплементации (наподобии Template Method, но все-таки не он).

Я рассматриваю два вида техники mixins, которые существуют в AS2: добавление методов в класс (через ключевое слово prototype) и добавление методов в экземпляр (как в рассмотренном выше примере). Оба эти способа широко используются в макромедийных классах.

Недостатки такого способа очевидны: мы теряем контроль над кодом. Здесь есть два уровня этого недостатка: концептуальный и уровень конкретной реализации. На концептуальном уровне беспокоит возможность такого поведения языка программирования в принципе. Я не могу доверять стороннему коду пока досконально его не изучу: в таком подходе структура кода на уровне сигнатур методов и определения класса (то есть, скажем, изучение intrinsic-определения класса если он поставляется без исходного кода) я не могу быть твердо уверенным, что этот код не содержит чего-то такого, что меняет поведение и моего кода! Я могу делать любые предположения при отладке, но действительность может сыграть со мной гораздо более злую шутку: для переопределения через прототипы нет модификаторов public/private (как нет их в AS1)!

Уровень же конкретной реализации как минимум может предоставить мне трудноразгадываемую головоломку при разборе кода. И как максимум (в качестве следствия) - неуправляемую сложность кода, с трудом поддающегося отладке (не поможет ни синтаксический анализатор, ни компилятор).

А плюс только один - легкий путь избавления от дублирования кода. Как будто нет наработанной базы приемов рефакторинга, паттернов проектирования (они все ведут к той же цели!).

Но есть еще более существенный довод в пользу того чтобы понимать компромиссную природу mixins и знать, что заставляет их нас использовать сейчас, но не полагаться на них при других условиях: мы имеем уникальную возможность заглянуть в будущее. В ActionScript 3 у нас не будет ни прототипирования, ни возможности переопределять методы экземпляров. Не верите? Что же, запустите нижеприведенный код в Flex 2 Alpha и убедитесь сами:


0: package cats.mixins {
1: import flash.util.trace;
2:
3: public class Client
4: {
5: public function Client ()
6: {
7: EventDispatcher.initialize (this);
8: }
9:
10: public function addEventListener(event:String, handler:Function):Void
11: {
12: trace ("own addEventListener");
13: }
14: }
15:
16: private class EventDispatcher
17: {
18: private static var _fEventDispatcher:EventDispatcher;
19:
20: internal static function initialize (object:Object):Void
21: {
22: if (EventDispatcher._fEventDispatcher == null)
23: {
24: EventDispatcher._fEventDispatcher = new EventDispatcher ();
25: }
26: object.addEventListener = _fEventDispatcher.addEventListener;
27: }
28:
29: private function addEventListener(event:String, handler:Function):Void
30: {
31: trace ("mixing addEventListener success!");
32: }
33: }
34: }


0: package
1: {
2: import flash.display.MovieClip;
3: import cats.mixins.Client;
4:
5: public class Cats extends MovieClip
6: {
7: public function Cats()
8: {
9: var aClient:Client = new Client ();
10: aClient.addEventListener ("someEvent", handler);
11: }
12:
13: private function handler ():Void
14: {
15:
16: }
17: }
18: }

Я просто имитировал ситуацию с mx.events.EventDispatcher, средствами AS3. И получил логичный runtime error:

ReferenceError: Error #1037: Cannot assign to a method addEventListener on cats.mixins.Client
at cats.mixins$6$private::EventDispatcher$/cats.mixins$internal::initialize()
at cats.mixins::Client$iinit()
at Cats$iinit()

И вот уже в AS3 проблема с механизмом обработки событий решена гораздо изящнее: класс flash.events.EventDispatcher является суперклассом ряда классов, которые в нем нуждаются. А если не является - можно включить его в качестве композита.

Думаю, что на этом вполне можно закончить. Если вам понравился данный материал, то просьба выражать благодарности его инициатору Евгению Н. Спасибо.

Комментариев 8:

Anonymous Fix сообщает:

А я в своем проекте EventDispatcher не пользую. Я когда начинал его писать, собственно и начал разбираться с AS2.0 читая Колина Мука. Там он описал несколько паттернов и разжевал класс EventBroadcaster. Я его себе взял за основу и у меня все UI наследуют от MovieClipOperator, который является декоратором для мувиклипа и в то же время наследует от EventBroadcaster.
Ну а если какой-то еще класс требуется для вещания событий, я использую композицию.

10 января, 2006 11:25  
Blogger Constantiner сообщает:

Ну так ты, видимо, не пользуешься v2 components. Иначе у тебя в приложении соседствуют два метода работы с событиями, что неудобно.

10 января, 2006 13:04  
Anonymous Fix сообщает:

Ты прав, не пользуюсь :) Приходится писать свои, что не менее неудобно :(

10 января, 2006 15:51  
Blogger Constantiner сообщает:

2fix
Не стОит, мне кажется, свои писать. Это отдельная большая работа не для одного человека, отдельный проект. И если не повторишь ошибки компонент v2, то своих понаделаешь :(

10 января, 2006 21:11  
Anonymous Fix сообщает:

К этому я уже и сам пришел. Но обратной дороги нет. :(
Просто на этапе планирования я столкнулся с тем, что компоненты непомерно увеличивают размеры swf. А мое приложение собирается из нескольких swf и в каждом из этих файлов могут быть компоненты, дублируя друг друга. Это сейчас, уже съев некоторое количество собак с шареными клипами, вынесением классов в библиотеки, я могу переосмыслить и прийти к выводу, что то же самое можно было бы проделать и с компонентами. Но кастомизация внешнего вида и расширение функционала все еще для меня гранит, который нужно грызть.. Я пошел по другому пути и пишу все сам, но только то что необходимо. Естественно я не реализую все компоненты какие есть в V2, более того, я их даже не делаю как компоненты, ибо это еще отдельная работа. И все равно это отобрало кучу времени и я не уверен что результат стоил затраченных усилий :(

11 января, 2006 12:51  
Blogger Constantiner сообщает:

2fix
Но зато ты приобрел ценный опыт :)

11 января, 2006 20:01  
Anonymous JabbyPanda сообщает:

Хотелось бы услышать мнение для проекта, который пишется на АС2.

Использовать EventBroadcaster класс как mix-in как компромисс в своих классах, так так Flex компоненты (Macromedia v3 компоненты )) используются постоянно в в проекте

Ж)

26 января, 2006 13:10  
Blogger Constantiner сообщает:

2JabbyPanda
Это вопрос? :)

26 января, 2006 21:08  

Отправить комментарий

Ссылки на пост:

Создать ссылку

Вернуться на главную