2012-02-24

Магические методы в ActionScript

Немного лирики

Оглядываясь на свой опыт разработки с использованием самых разных языков программирования, я для себя могу выделить более удобные для решения типовых задач и оптимальные по количеству кода. Не важно на что ориентирован язык, под типовыми задачами я подразумеваю, к примеру, различные операции с массивами, объектами, классами, переменными и т.д. и .т.п. В решении типовых задач задействуется богатство средств ООП языка программирования, явная или не явная типизация и весь тот синтаксический сахар, позволяющий сократить код с сохранением приемлемого уровня читабельности и др. и пр. Как показывает практика, один и тот же функционал с использованием различных языков может достаточно сильно отличаться по количеству кода. Так же привыкнув к возможностям одного языка, порой приходится искать подобную реализацию в другом.

Конкретней...


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

Какая-то сферическая задача в вакууме

При решении одной задачи на ActionScript меня озаботила проблема отсутствия "магических" методов, аналогов "__get" и "__set" в PHP или "method_missing" в Ruby. Но, как оказалось, по результатам поиска, аналоги выполняющие функции "магических" методов все же есть в ActionScript, в третьей версии предлагается к использованию класс Proxy.

Попробуем разобраться и реализовать что-нибудь полезное, чтобы облегчить себе жизнь. Да и, вообще, программистами движет лень - ведь, чего только не придумают, чтобы меньше тратить времени на кодинг :-).

Приступаем к практике

Попробуем создать класс для хранения каких-нибудь свойств, список которых нам заранее не известен. Вообще, тут мы могли бы использовать и простой Object или Dictionary, но пусть это будет задел на будущее, так что будем работать с Proxy.

Допустим, в проекте мы описали модель магического предмета Thing. Одним из свойств модели вещи будет ее предназначение (модель Uses), которое, в свою очередь, тоже имеет различные параметры.

Uses.as

package {
 import flash.utils.Dictionary;
 import flash.utils.Proxy;
 import flash.utils.flash_proxy;
 
 dynamic public class Uses extends Proxy {
  
  // список всех возможных свойств
  public const AVAILABLE:Array = ['weight', 'light', 'speed', 'altitude'];
  
  // заданные для конкретного предназначения параметры
  private var _props:Dictionary = new Dictionary();
  
  public function Uses() {
   // constructor
  }
  
  // разрешенному свойству применим метод
  override flash_proxy function callProperty(method_name:*, ... args):* {
   var action_name:Array = String(method_name.localName).split('_');
   var action:String = action_name[0];
   var name:String = action_name[1];
   if (AVAILABLE.indexOf(name) < 0) return undefined;
   switch (action) {
    case 'inc': // увеличим значение свойства
     _props[name] += 1;
     break;
    case 'dec': // уменьшим значение свойства
     _props[name] -= 1;
     break;
    case 'clr': // сбросим в 0 значение свойства
     _props[name] = 0;
     break;
    default:
     // тут можно что-нибудь еще придумать
     break;
   }
   return _props[name];
  }
  
  // получим значение свойства
  override flash_proxy function getProperty(name:*):* {
   if (AVAILABLE.indexOf(name is QName ? name.localName : name) < 0) return undefined;
   return _props[name];
  }
  
  // установим значение свойства
  override flash_proxy function setProperty(name:*, value:*):void {
   if (AVAILABLE.indexOf(name is QName ? name.localName : name) < 0) return ;
   _props[name] = value;
  }
  
  // проверим наличие свойства
  override flash_proxy function hasProperty(name:*):Boolean {
   return _props.hasOwnProperty(name is QName ? name.localName : name);
  }
  
 }
}

Main.as

package 
{
 import flash.display.Sprite;
 public class Main extends Sprite
 {
  public function Main():void 
  {
   var uses:Uses;
   
   // инициализируем набор предназначений
   uses = new Uses();
   uses.weight = 50;
   uses.altitude = 100;
   
   // определено?
   trace('weight' in uses); // true
   // определено?
   trace('light' in uses); // false
   
   // пробуем магические методы
   uses.inc_weight();
   uses.dec_altitude();
   
   // запрашиваем
   trace(uses.weight); // 51
   trace(uses.altitude); // 99
   // а если не определено?
   trace(uses.xxx); // undefined
   
  }
 }
}

Итого

Вот так мы сделали еще один шаг на светлую сторону. Далее включаем режим "творчество" и начинаем использовать описанные возможности на практике, там где оно действительно нужно.