Рубрика: AS3
Package organization
Организация кода в пакеты
Словарь
Рефакторинг - Классическая книга описывающая практики рефакторинга - http://www.ozon.ru/context/detail/id/1308678/
DSL - Domain-specific language
Введение
Пакеты это механизм для организации кода и разрешения конфликта уникальности наименования классов. Это техническое определение, но по какому принципу создавать наименования? Какими критериями руководствоваться? И зачем вообще об этом задумываться?
К сожалению этот вопрос слабо освещен в классической литературе. В данной статье я хочу поделиться своим опытом и некоторыми исследованиями по данному вопросу. Первая часть статьи это моя попытка выделить общие принципы. В частности много идей я подчеркнул из презентации Juergen Hoeller, http://www.infoq.com/presentations/code-organization-large-project. Вторая часть статьи - описание вымышленного flex проекта и пошаговое развитие его структуры на основе сформулированных принципов и моего личного опыта.
Общие принципы
Можно сформулировать следующие архитектурные принципы:
- Избегать циклических зависимостей;
- Избегать дублирования;
- Формировать модули;
- Стремиться создавать слабо связанные модули;
- Выбирать модули на основе логической, концептуальной организации (домена)
Далее о каждом их них более подробно:
Избегать циклических зависимостей между пакетами

Циклическая зависимость это когда два пакета ссылаются друг на друга. Обычно такая зависимость не планируется и появляется в последствии. Часто это может быть признаком ухудшения общего состояния кода. Создание пакетов - динамический процесс. Со временем приложение меняется, появляется новая функциональность, старая переосмысливается. Возможно вам придется разделить пакеты по разным приложениям(библиотекам) и компилировать по отдельности. Циклическая зависимость усложняет понимание и развитие кода. Не дает его повторно использовать.
Избегать дублирования
Часто альтернативой циклической зависимости является дублирование кода. Но такой код уже плохо пахнет. Дублирование загромождает код и затрудняет его модификацию. Попробуйте переосмыслить местоположение нового кода и не прятать его глубоко в иерархии.
Формировать модули
Модуль это концептуальная сущность содержащая один или несколько дочерних пакетов. Модуль определяет границы для используемых в нем пакетов.
Стремиться создавать слабо связанные модули

Слабая связность модулей также обеспечивает более понятный код и облегчает повторное использование.
Выбирать модули на основе логической, концептуальной организации (домена)
Часто модули разделяют на основе типов, например:

или на основе уровней:

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

Зачем об этом заботиться?
Правильная организация пакетов и модулей позволит:
- создать устойчивый внешний API для поддержки обратной совместимости;
- уменьшить время на понимание кода;
- упростить тестирование;
- облегчить повторное использование;
- уменьшить время компиляции, что немаловажно для большого приложения;
- привнести в работу программиста творчество.
Пример и несколько практических советов
В последнее время я часто встречаю в flex приложении структуру пакетов следуещего вида:
vo
view
model
control
commands
business
events
Такое разделение на пакеты пришло к нам из фреймворка Cairngorm, где, видимо его применение оправдано. Но оно встречается и в приложениях, где Cairngorm не используется. Как показывает моя практика, такое разделение плохо развивается при разрастании приложения, пакеты получаются сильно связанные и их практически невозможно использовать повторно в другом приложении.
Я использую разбиение по логике или функциональности. Попробую привести пример.
Рассмотрим приложение AdminPanel
Здесь будет показан сам принцип создания пакетов. Не надо воспринимать получившуюся структуру как эталон, в реальном приложение много дополнительных деталей и специфики.
Начальная структура
rootpackage = com.agakhov.examples.adminpanel
rootpackage.ApplicationView.mxml // определяется размещение всех компонентов приложения
rootpackage.MainDataModel.as // модель данных всего приложения
rootpackage.MainEventMap.xml // сущность из фреймворка mate, её можно заменить обычным классом контроллера
rootpackage.AdminApplucationEvent.as // событие уровня приложения, например ApplicationStart
defaultpackage.Main.xml //создает и инициализирует основные компоненты)
То, что я делал до сих пор, относилось ко всему приложению, поэтому я не выделял никаких специализированных пакетов. Приложение запускается показывает пустое окно и закрывается. Но уже работает.
Login
Допустим, что первая по приоритету задача это аутентификация, для неё я создаю пакет login, собирающий все что связано с этой задачей:
rootpackage.login.LoginView
rootpackage.login.LoginEvent
rootpackage.login.LoginLocalEventMap // содержит методы обращения к сервису
rootpackage.login.LoginModel
Теперь после загрузки наше приложение открывает форму авторизации LoginView и после её заполнения отправляет запрос на сервер. В случае успешной аутентификация и авторизации, посылается сообщение основному приложению и закрывается LoginView.
Registration
Возможно, данного пользователя нет в системе и он захочет зарегистрироваться. Для этого мы создаем пакет registration
rootpackage.registration.RegistrationView
rootpackage.registration.RegistrationEvent // события регистрации
rootpackage.registration.RegistrationlEventMap // содержит методы обращения к сервису регистрации
rootpackage.registration.RegistrationModel // данные о регистрации
Теперь пользователь может открыть окно регистрации, заполнить поля. Если все в порядке, информация отправляется на сервер.
В случае удовлетворительного ответа от сервера, RegistrationView закрывается, основное приложение получает соответствующее сообщение. Дальнейшее развитие этих пакетов зависит от сложности процесса регистрации и авторизации, например регистрация может использовать OpenId, капчу, автозаполнение и т.п. Если процессы многообразны, то возможно создать для их обработки отдельную EventMap. Некоторые функции попросятся в собственный модуль. Но сосредоточимся пока на доменах registration и login

Переход к высокоуровневому домену
Сейчас явно напрашивается желание собрать регистрацию и логи в общий пакет (модуль).
Я придумал наименование accessmanagment.
rootpackage.accessmanagment.AccessManagementModel.as
rootpackage.accessmanagment.AccessManagementLocalEventMap.xml
rootpackage.accessmanagment.AccessManagmentEvent
rootpackage.accessmanagment.login.LoginView
rootpackage.accessmanagment.login.LoginEvent
rootpackage.accessmanagment.login.LoginLocalEventMap // содержит методы обращения к сервису
rootpackage.accessmanagment.login.LoginModel
rootpackage.accessmanagment.registration
rootpackage.accessmanagment.registration.RegistrationView
rootpackage.accessmanagment.registration.RegistrationEvent
rootpackage.accessmanagment.registration.RegistrationlEventMap
rootpackage.accessmanagment.registration.RegistrationModel
Ортогональный домен
Думаю, идея создания пакетов на основании домена понятна, но напрашивается вопрос, что делать, если один и тот же класс необходимо использовать в нескольких модулях?
Для задач ортогональных домену предметной области я обычно завожу папку common на соответствующем уровне иерархии. Например мне необходимо выделить базовый класс для всех mate моделей или общего предка для всех модальных окон:
rootpackage.ApplicationView.mxml
rootpackage.MainDataModel.as
rootpackage.MainEventMap.xml
rootpackage.AdminApplucationEvent.as
defaultpackage.Main.xml
rootpackage.common
rootpackage.common.model.AbstractMateModel.as
rootpackage.common.view.ModalView.as
rootpackage.accessmanagment.*
rootpackage.accessmanagment.login.*
rootpackage.accessmanagment.registration.*
Для пакета common основным доменом будет именно ортогональная задача. Далее сommon может быть вынесен в самостоятельный проект используемый различными приложениями. Собственно, это и есть его продвижение по иерархии. Если мы выделим common на уровне accessmanagment, он будет использоваться задачами регистрация и аутентификация. Далее он может полностью или частично двигаться вверх в common для всего приложения и еще выше для нескольких приложений. Ну и наконец превратиться в публичный API.
Резюме
Основную идею которую я хотел передать - создание пакетов на основе домена предметной области. Такая техника оправдывает себя в моей практике. Хорошо работает в больших проектах. Для маленьких проектов я бы рекомендовал не создавать пакетов совсем. Зачем два события отделять в отдельный пакет? Как только приложение начнет расти, появятся пакеты предметной области это позволит всегда работать с маленьким приложением.
Ссылки
Всем заинтересовавшимся вопросом рекомендую к ознакомлению видео презентацию Juergen Hoeller, одного из основателей Spring Framework http://www.infoq.com/presentations/code-organization-large-projects и (или) её текстовые пересказы http://mikenereson.blogspot.com/2007/06/spring-on-code-organization-for-large.html,
http://www.kimchy.org/spring-one-code-organization/
Немного странный источник, но идеи примерно те же http://docstore.mik.ua/orelly/oracle/advprog/ch02_01.htm
Flex Mojos - A Maven Flex Plugin
Недавно вышел новый Flex Mojos - A Maven Flex Plugin. Если вы собираетесь внедрять maven, рекомендую к нему присмотреться.
Сборка приложения использующего BlazeDS, Spring, Hibernate. очень хорошо описана в наборе статей (Flex, Spring and BlazeDS: the full stack! part 1, part2 , part3, part4)
Flex + Maven часть1
Для сборки flex c помощью maven необходимо произвести следующие действия:
- установить MAVEN
- установить flex sdk
- создать файл проекта pom.xml
- настроить среду
- создать структуру проекта и Main.mxml
- запустить maven
Создание файла проекта pom.xml
плагин для сборки swf/swc http://www.israfil.net
пример pom.xml для flex
XML:
<?xml version="1.0" encoding="UTF-8"?> | |
<project xmlns="http://maven.apache.org/POM/4.0.0" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 | |
http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
| |
<name>test-flex-swf</name> | |
<groupid>test</groupid> | |
<artifactid>test-flex-swf</artifactid> | |
<version>1.0</version> | |
<modelversion>4.0.0</modelversion> | |
<description>test build flex with maven. | |
see: </description>">http://riapriority.com/blogs/agahov.php</description> | |
| |
<packaging>swf</packaging> | |
| |
<properties> | |
<flex .home>C:/FLEX_HOME/sdk/2.0.1</flex> | |
</properties> | |
<build> | |
<plugins> | |
<plugin> | |
<groupid>net.israfil.mojo</groupid> | |
<artifactid>maven-flex2-plugin</artifactid> | |
<extensions>true</extensions> | |
<configuration> | |
<flexhome>${flex.home}</flexhome> | |
<mainmxmlfile>Main.mxml</mainmxmlfile> | |
</configuration> | |
</plugin> | |
</plugins> | |
</build> | |
</project> |
Настройка среды
- flex.home в pom.xml должна ссылаться на flex sdk, которую вы хоти использовать для сборки
не забудте в файле $flex.home/frameworks/flex-config.xml отредактировать следующую строку:
winFonts.ser - для windows
macFonts.ser - для MAC
XML:
<flex -config> | |
<compiler> | |
<fonts> | |
<local -fonts-snapshot>---Fonts.ser</local> | |
</fonts> | |
</compiler> | |
</flex> |
Cтруктура проекта
project-name/pom.xml
project-name/src
project-name/src/main/
project-name/src/main/flex
project-name/src/test
project-name/src/main/flex/Main.mxml
Main.mxml может быть таким:
XML:
<?xml version="1.0" encoding="utf-8"?> | |
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> | |
<mx:Script> | |
<![CDATA[ | |
import mx.controls.Alert; | |
| |
private function start():void | |
{ | |
Alert.show("hi maven!"); | |
} | |
]]> | |
</mx:Script> | |
| |
<mx:Button click="start()"/> | |
</mx:Application> |
Сборка проекта с помощю maven
для сборки проекта зайдите в папку [project-name] и выполните команду: mvn pakage
Конференция User Experience
8-10 ноября прошла конференция User Experience. Это направление охватывает создание и использование продукта в целом. Основная цель User Experience - полезность (valuable) продукта для конечного пользователя.
Доклады, которые хотелось бы выделить.
Клонирование объектов утилитой ObjectUtil.copy
Нам понадобилось клонировать объект. Для этого в as3 можно использовать утилиту ObjectUtil.copy. Метод ObjectUtil.copy(obj) возвращает копию исходного объекта obj. Это замечательно работает для невизуальных объектов.
Пример:
- package com.test
- {
- public class TestClass
- {
- public var index : int = 100;
- }
- }
- ...
- var testObject:TestClass = new TestClass();
- testObject.index = 98;
- var cloneTestObject : Object = ObjectUtil.copy(testObject);
- trace(“cloneTestObject.index : ” + cloneTestObject); // cloneTestObject.index : 98
- ...
Но есть ложка дегтя. Данная функция возвращает анонимный объект. И система не позволяет осуществить его приведение к исходному классу.
Данный код приведет к сбою в программе:
- ...
- var testObject:TestClass = new TestClass();
- testObject = 99;
- var cloneTestObject : TestClass = ObjectUtil.copy(testObject) as TestClass;
- trace(“index: ”+ cloneTestObject.index);
- ...
После некоторых исследований и подсказки Константина Ковалёва, находим метод flash.net.registerClassAlias. Который и решает проблему:
- ...
- var testObject:TestClass = new TestClass();
- registerClassAlias(getQualifiedClassName(testObject), TestClass);
- var cloneTestObject : TestClass = ObjectUtil.copy(testObject) as TestClass;
- ...
Для тех, кто хочет докопаться до сути процесса. В справке по registerClassAlias написано: «LocalConnection, ByteArray, SharedObject, NetConnection and NetStream are all examples of classes that encode objects in AMF.» Так как метод ObjectUtil.copy, использует ByteArray, то наш результирующий объект кодируется в формат AMF. Для восстановления класса объекта, используется функция flash.net.registerClassAlias.
Решение проблемы наследования от Point.
Проблема:
Допустим, сделал я интерфейс IVector:
- interface IVector{
- function get x () : Number;
- …
- }
Есть у меня класс утилита, который работает с этим интерфейсом:
- class VectorMath{
- public function calc(v:IVector):IVector
- {
- …
- }
- …
- }
Кульминация - нужно сделать реализацию IVector:
- class Vector implements IVector
- {
- private var _x : Number;
- public function get x () : Number
- {
- return _x;
- }
- …
- }
Если присмотреться к классу Vector, становиться понятно, что он очень похож на Point.
Что же делать, ведь в данной реализации наследовать Vector от Point нельзя. Включать Point в Vector тоже нехорошо, потому как мы потеряем поведение Point. И для всех системных утилит принимающих Point, придется делать преобразование.
Решение:
Перепишем интерфейс IVector:
- interface IVector
- {
- function get i () : Number;
- }
И реализация IVector будет иметь следующий вид:
- class Vector extends Point implements IVector
- {
- public function get i () : Number
- {
- //это не опечатка :)
- return super.x;
- }
- …
- }
Вот такая радость
. Сегодня придумал. Если вы увидели недостатки данного решения, очень прошу поделиться.
Публичные переменные в классе flash.geom.Point
Дополнение к посту «Публичные переменные это зло… »
Постараюсь более подробно раскрыть проблему реализации системного класса flash.geom.Point.
Изучим структуру flash.geom.Point:
- var myTestPoint : Point = new Point(9,9);
- trace(describeType(myTestPoint));
в данные момент меня интересуют две строчки, которые говорят что свойства x, y класса flash.geom.Point, описаны как публичные переменные
…
variable name="x” type="Number”
…
variable name="y” type="Number”
…
какие проблемы это вызывает?
Отсутствует возможность переопределить данные свойства в классе потомке.
Пример 1:
- Point;
- public class MyPoint extends Point
- {
- public function set x (v:Number)
- {
- x = v;
- }
- }
- }
error: 1024: Overriding a function that is not marked for override.
Пример 2:
- package
- {
- import flash.geom.Point;
- public class MyPoint extends Point
- {
- public override function set x (v:Number)
- {
- x = v;
- }
- }
- }
error: 1023: Incompatible override.
Следовательно для реализации, например, интерфейса IPoint
- package
- {
- public class IPoint
- {
- function set x (v : Number) : void;
- function get x () : Number;
- function set y (v : Number) : void;
- function get y () : Number;
- }
- }
нельзя использовать использовать наследование
- public class MyPoint extends Point implements IPoint
придется использовать делегацию:
- package
- {
- import flash.geom.Point;
- public class MyPoint implements IPoint
- {
- private var _point : Point;
- public function MyPoint ()
- {
- _point = new Point();
- }
- public function set x (v:Number):void
- {
- _point.x = v;
- }
- public function get x ():Number
- {
- return _point.x;
- }
- public function set y (v:Number):void
- {
- _point.y = v;
- }
- public function get y ():Number
- {
- return _point.y;
- }
- …
- }
- }
Делегация в данном случае не оправдана, так как класс MyPoint нельзя будет использовать в методах работающих с Point. Вокруг стандартных методов придётся писать обёртки или использовать преобразование.
Пример преобразования:
…
- var Rect:Rectangle = new Rectangle(0,0, 100, 100);
- Rect.offsetPoint( new Point(myPoint.x, myPoint.y) );
…
Пример обертки:
- package
- {
- import flash.geom.Rectangle;
- import flash.geom.Point;
- public class MyRectangle extends Rectangle
- {
- public function offsetIPoint ( point : IPoint)
- {
- offsetPoint (new Point ( point.x, point.y ));
- }
- }
- }
flex-coding-guidelines
Fabio Terracini опубликовал в рассылке FlexCoders рекомендации по оформлению кода для Flex (AS3, MXML, CSS).
ссылка на pdf файл.
Данный документ основан на соглашениях используемых в Adobe Flex libararies, Java
и работе Fabio в DClick.
Пример неудачного, использования CairngormEventDispatcher
Краткое описание действующих лиц.
Паттерны программирования
http://en.wikipedia.org/wiki/Design_pattern_(computer_science)
http://zeus.sai.msu.ru:7000/SE/project/pattern/index.shtml
http://www.exciton.cs.rice.edu/JavaResources/DesignPatterns/singleton.htm
http://www.csc.calpoly.edu/~dbutler/tutorials/winter96/patterns/
J2EE (Java 2 Enterprise Eddition)
http://ru.sun.com/java/j2ee/index.html
http://www.codenet.ru/webmast/java/j2ee.php
J2EE Patterns
http://java.sun.com/blueprints/patterns/catalog.html
http://www.patterndepot.com/put/8/JavaPatterns.htm
Cairngorm Микро архитектурный фреймворк, основанный на J2EE паттернах, адаптированных для flex /flash (ria). Т.е. это реализация нескольких паттернов для flex и общая рекомендация о связке между ними.
CairngormEventDispatcher – Централизованный диспетчер событий, Singleton.
Позволяет послать событие из любой точки программы.
CairngormEvent – пользовательское событие, его можно переопределить для отсылки данных вместе с событием.
MyEvents.MY_EVENT – имя пользовательского события, которое ассоциируется с командой.
отсылаем событие:
- var theMyEvent : CairngormEvent = new CairngormEvent( MyEvents.MY_EVENT );
- CairngormEventDispatcher.getInstance().dispatchEvent ( theMyEvent );
Архитектура Cairngorm подразумевает, что обработка этих событий будет реализована через FrontController, который определяет соответствие событий и команд(ICommand). Другими словами, обработка реакции на события происходит централизованно.
Но существует возможность подписаться на это событие в любом месте приложения. Без использования команд и FrontController.
подписываемся на событие без использования комманд:
- CairngormEventDispatcher.getInstance().addEventListener ( MyEvents.MY_EVENT, MySuperHandler );
На первый взгляд данная возможность впечатляет: кто угодно может обратиться к кому угодно из любой точки приложения.
Но если Вы напишите достаточно много таких вызовов, то потеряете контроль над своим приложением. Особенно, если работаете в команде над общим кодом.
Первая проблема, которую я вижу, это циклический вызов. Остальные предоставлю вам найти самостоятельно, если всё же решите использовать данную конструкцию.
Публичные переменные...это зло
Вместо публичных переменных, нужно использовать свойства (get/set).
Как миниму, свойства позволяют контролировать доступ к внешним переменным класса.
Отход от этой практики приводит к нескольким проблемам. Особенно заметных в публичных библиотеках.
Примером НЕ использования свойств может служить стандартный класс as3 flash.geom.Point, в котором атрибуты x и y определены как публичные переменные.
Что из этого следует ?
Теперь нельзя переопределить поведение атрибутов x и y в классе наследнике.
Нельзя сделать интерфейс IPoint, c нормальными свойствами, для класса наследника Point, следовательно нельзя организовать множественное наследование от Point.
Как с этим жить?
1) Сделать свой класс который не наследуется, от Point, а включает его, этот вариант решает проблемы для работы с новыми классами принимающими IPoint но,
с методами которые принимают Point работать будет тяжело.
В дополнение придётся прописать у CustomPoint свойство get point,
возвращающее Point для передачи и set point для применения значения. О передаче значения по ссылке придётся забыть.
2)Ещё как вариант сделать обёртки над классами принимающими Point, поменять их на IPoint, но это сразу видно работа не благодарная…
так, что без кровного решения проблемы я пока не нашел….