bindx (связывание данных)

Под впечатлением от крайней конференции я сделал для себя массу выводов. Одним из которых и станет эта статья. Речь в статье пойдет о библиотеке bindx.

Но для начала или наоборот по прочтению статьи советую посмотреть мою презентацию на конференции об этой же библиотеке:

Data binding
Если снова обратиться к википедии, а именно к определению понятия Data binding, то мы сразу узнаем одну из основных задач, для чего биндинги могут быть использованны - связь GUI с моделью.

Вообще data binding можно и стоит переводить как связывание данных, а не называть заморским и не всегда понятным словом - биндинги. Из понятия “связывание данных” понятно, что мы связываем что-то, а связь подразумевает, что у нас есть как минимум два операнда, которых мы собственно и связываем. Но хватит ванговать, смотрим картинку:

http://i.msdn.microsoft.com/dynimg/IC205083.png

Кто пошел по ссылке выше на msdn, тот скорее всего ее уже нашел. Видно что у нас есть источник и целевой объект, источних предоставляет нам данные, которые мы и передаем приемнику. Но в отличии от простой передачи свойсва, тут происходит именно связывание (поэтому двухстронняя стрелочка, хоть данные бегают лишь в одну сторону). Связывание данных позволяет целевому объекту быть всегда вкурсе о значении свойства и своевременное оповещает его об этом изменении.

На самом деле связований бывает несколько видов, не говоря уже о самих реализация, но все это можно узнать из просторов интернета. Теперь, когда мы хоть примерно знаем как должно работать связывание, подумаем, где бы мы могли это использовать в Haxe разработке. Но давайте рассмортим применение внимательнее, а уже потом вернемся к самой либе.

Применение
Чтобы не придумывать, я и AxGord лишь расскажем, где реально мы это используем: (а в дальшейшем надеюсь не только мы)

  • Deep:
    Для UI контролов, которые позволяют связывать все свойства контролов с другими контролами и позволяет легко общаться соседним или дочерним и родительским нодам.
  • AxGord:
    В моделях симуляции физических процессов. Модели подключаются к Flash или к Unity в зависимости от проекта. В основном использование флагов состояний.

    Привязка логики модели к визуальной части. Самое частое и простое вывод текста. Используется как для Unity так и для Flash платформы.

Все описанные выше примеры - это лишь малая часть, где можно использовать связывание данных. Но даже их, должно хватить, чтобы заинтересовать вас.

bindx (bindx.IBindable)
Вернемся к нашим баранам и рассмотрим основные возможности, которые предоставляет наша библиотека bindx.

Для начала вам надо знать, что все что вам надо - это интерфейс bindx.IBindable и утилитарный класс bindx.Bind, который чаще всего вы будете использовать через using bindx.Bind. Ну еще не стоит забывать про мета данные, которые помечают свойства для связывания.

Рассмотрим простой пример класса реализующего интерфейс IBindable

@bindable class Value implements IBindable {

    public var a:Int;

    // не меняет сеттер
    @bindable({force:true}) public var b(get, set):Int;

    function get_b():Int {
        return a;
    }

    function set_b(v:Int):Int {
        var old = a;
        Bind.notify(this.b, old, a = v); // this.b.notify(old, a = v);
        return a;
    }

    public function new() {

    }
}

С первой же строчки мы видим мету @bindable перед определением класса, она не обязательная, но ее наличие говорит библиотеке, о том чтобы всем публичным переменным с сеттером default или set автоматически добавлялась мета @bindable. Это очень удобно и позволяет сильно сократить кол-во набираемых символов. В нашем примере переменная a получит мету @bindable, т.к. соответствует всем условиям.

Значит еще раз, @bindable перед классом автоматически добавит мету @bindable всем

  1. публичным переменным
  2. сеттер: default или set
  3. @bindable не была заданна вручную

Далее видно, что у свойства b заданные дополнительные параметры в мете. На сегодняшний день поддерживаются два дополнительных свойства:

  • force (по умолчанию false) - в случае true не смотрит на сеттер и никак его не меняет (об этом подробнее ниже). Force флаг очень нужен в случае когда сеттер указан как never, null или dynamic
  • inlineSetter (по умолчанию true) - в случае генерации сеттера для свойства этот флаг отвечает, будет ли сеттер заинлайнен или нет.

Теперь когда мы немного разобрались с мета данными, самое время разобраться, зачем же мы их так тщательно расставили. Дело в том, что все @bindable переменные или почти все, подвергаются сильным изменениям, а именно для default сеттера создается сеттер функция и переменные типа var a:Int; становятся переменными типа var a(default, set):Int, а в случае если сеттер уже существовал, то тело функции сеттера немного изменится. Сделано это все, для того, чтобы оповещать остальных, об изменениях @bindable свойств.

Рассмотрим отдельно два случая:

  • var a:Int;
    В данном случае сгенерируется простой сеттер, примерно такого вида
    inline function set_a(value) {
            var oldValue = this.a;
            if (oldValue == value) return value;
            else {
                    this.a = value;
                    this.__fieldsBindings__.dispatch(“a”, oldValue, this.a);
                    return this.a;
            }
    }

    Inline метод будет если не указать дополнительный флаг inlineSetter = false.

  • var b(default, set):Int;
    В данном случае все намного сложнее, но и его можно схематически продемонстировать. Допустим старый сеттер выглядил так

    function set_b(value) {
            // setter body
            return value;
    }

    тогда измененный сеттер с поддержкой связывания данных будет выглядеть так:

    function set_b(value) {
            var oldValue = this.b;
            if (oldValue == value) return value;
            else {
                    //setter body
                    {
                            this.__fieldsBindings__.dispatch(“b”, oldValue, this.b);
                            return value;
                    }
            }
    }

Первое, что бросается в глаза, это проверка с предыдущим значением, это необходимо, чтобы не было ложных срабатываний сеттера и в большинстве случаев никак не повредит вашему сеттеру, а даже напротив, позволяет вам не делать такую проверку самому (для остальных случаев, когда такая проверка только мешает есть флаг force (см. выше)). Ну и второе изменение сеттера, это диспатч изменения прямо перед самым ретурном. И не стоит боятся использовать сложные конструкции в ретурнах, типа return this.a = value; библиотека отлично обработает их, правда изменив на боле сложную конструкцию:

this.a = value;
this.__fieldsBindings__.dispatch(“a”, oldValue, this.a);
return this.a;

Это нужно знать и учитывать, особенно если у переменной a есть геттер и вам не хочется, чтобы он срабатывал несколько раз за один сеттер.

Теперь, когда вы точно знаете, что на самом деле сделает библиотека с @bindable свойствами, я могу спать спокойно. Ну т.е. пора переходить к загадочному __fieldsBindings__. На самом деле таких загадочных свойства у всех IBindable классов целых два. Они даже прямо прописанны в интерфейсе:

interface IBindable
{
        public var __fieldBindings__(default, null):FieldsBindSignal;
        public var __methodBindings__(default, null):MethodsBindSignal;
}

Но определять их в классах не обязательно, я бы даже рекомендовал никогда не делать этого, оставив и это грязное дело библиотеке. Все что надо от вас, то это обязательно определять конструктор во всех IBindable классах. Это нужно, чтобы встроить в него инициализаторы сигналов.

Но вернемся к нашим сигналам, __fieldBindings__ оповещают в случае, если хоть одно свойство изменилось, __methodBindings__ - в случае методов (про биндинг методов смотрите в моем видео). Но ни те ни другие в 99% случаев вам напрямую использовать не понадобится и я снова рекомендовал бы вам не трогать их никогда, разве что в деструкторе, в котором можно вызвать destroy() метод у обоих сигналов, чтобы наверняка отписаться от всех слушателей и позволить сборщику мусора сделать свое грязное дело с легкостью. Самые любознательные могут изучить код самих сигналов и может даже оптимизировать их, т.к. их скорость это самое узкое место библиотеки.

Уже тут у вас должно были сложиться вырожение лица аналогичное смайлу 0_o, по крайней мере именно к этому эффекту стремиться библиотека bindx. И вы просто не поверите, но впереди еще масса интересного. Интересного еще настолько много, что представить это в данной статье не вижу возможным. Поэтому на этом мы пока остановимся, но это остановка на полуслове и скоро будет продолжение. А пока небольшие выводы.

Вывод
В данной статье мы немного узнали о связывании данных, мы почти полностью узнали как работает макрос (а-а-а, я впервые за всю статью использовал это слово, а ведь на самом деле 90% работы делают в либе именно макросы), который автоматически вызывается в случае реализации bindx.IBindable интерфейса. Мы узнали некоторые важные обстоятельства поведения биндингов, которых нет даже в документации (а все потому что, документации тоже нет). Если вернуться к нашей первой картинке, то видно, что мы изучили то как bindx подготавливает источник привязки.

Что нам осталось? Осталось изучить как нам к этому источнику привязываться. Об этом в следующей статье.

P.S. Установить библиотеку можно и через haxelib:

haxelib install bindx

Отдельная благодарность AxGord-у, SlavaRa, Харват Егору и Бурдун Александру за помощь в написании статьи.