Skip to content
Grigoriev Oleg edited this page Mar 10, 2013 · 2 revisions

go.Class: мутаторы

Мутатор, это объект, который может оказать влияние на структуру класса и каждого его экземпляра.

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

С помощью мутаторов реализованы, например, статические поля и связь методов с контекстом.

Определение мутаторов

var TestClass = go.Class(ParentClass, {

    '__mutators': {
        'one': {
            'processClass': function (props) {
                // ...
            }
        },
        
        'two': {        
            'processInstance': function (instance) {
                // ...
            }
        }
    },
    
    function method() {
        // ...
    }
});

Для определения мутаторов используется поле __mutators. В примере определены два мутатора one и two.

Мутаторы наследуются от внутреннего класса Mutator, который расширяют. В примере one переопределил метод базового мутатора processClass().

Если одноимённые мутаторы уже были определены в предках (например, в ParentClass), то они расширяются здесь.

Методы и свойства класса Mutator

Базовые методы, предназначенные для переопределения:

  • processClass(Object props) - вызывается при создании класса. Получает список полей, переданных в функцию go.Class() и может изменить их.
  • processInstance(Object instance) - получает только что созданный экземпляр класса. Может изменить его, как хочет. Вызывается до того, как сработает конструктор instance.__construct().
  • getMethod(String name, Object instance) - доступ к сохранённому методу. Подробнее ниже.

Доступные свойства:

  • name - имя мутатора
  • Class - класс к которому привязан
  • parent - класс из которого наследован мутатор
  • fields - сохраняемые поля. Копируются из родительского мутатора. См. ниже.

Объект списка мутаторов хранится в свойстве класса __mutators. Сам массив мутаторов хранится в свойстве mutators списка.

То есть, мутатор mymutator для класса MyClass доступен, как MyClass.__mutators.mutators.mymutator.

Стандартные мутаторы

У всех классов есть три стандартных мутатора (определённых в базовом классе go.Class.Root). Это sysvars, static и bind. На их примерах и рассмотрим постороение мутатора.

Кроме того, мутаторы используются в go.Ext.Options и go.Ext.Nodes.

Мутатор sysvars

Мутатор sysvars обрабатывает такие системные поля, как __abstract и __final.

'sysvars': {
    'vars' : {
        '__abstract'  : false,
        '__final'     : false,
        '__classname' : "go.class"
    },
    'processClass': function (props) {
        var C = this.Class,
            vars = this.vars,
            name;
        for (name in vars) {
            if (vars.hasOwnProperty(name)) {
                if (props.hasOwnProperty(name)) {
                    C[name] = props[name];
                    delete props[name];
                } else {
                    C[name] = vars[name];
                }
            }
        }
        delete props.eoc;
    }
},

processClass() вызывается в момент создания класса. Переданные в качестве аргумента в go.Class() поля класса пропускаются через мутаторы и могут быть изменены, прежде чем попадут в прототип нового класса.

Здесь такие поля, как __abstract, __final и __classname переносятся в объект класса (this.Class) и удаляются из его прототипа (props). Заодно удаляется eoc. Если нужные поля не определены, используется значение по умолчанию (не абстрактный и не финальный класс).

Кроме переопределения базовых методов можно определить какие угодно свои методы и свойства. В примере, это vars: список системных полей и их значения по умолчанию.

Мутатор static

Мутатор static отвечает за статические свойства.

'static': {
    'processClass': function (props) {
        var C  = this.Class,
            st = props.__static,
            fields,
            k;
        fields = this.fields;
        if (st) {
            go.Lang.extend(fields, st);
            delete props.__static;
        }
        for (k in fields) {
            if (fields.hasOwnProperty(k)) {
                C[k] = fields[k];
            }
        }
    }
},

Мутатору требуется сделать статическими все поля, определённые в свойстве __static. То есть перенести их все в объект this.Class и удалить само свойство props.__static из прототипа.

По сравнению с sysvars одно усложнение: статические свойства должны наследоваться, так что this.Class должен быть наполнен также и статическими свойствами, определёнными в его родительском классе.

Здесь используется свойство fields. При создании мутатора, оно копируется из одноимённого мутатора родительского класса (если он там определён). Именно копируется, то есть создаётся новый объект и туда переносятся все свойства, так что при изменении родительский fields не будет затронут.

В приведённом коде все поля из __static копируются в fields. А уже из него в класс.

При наследовании класса, мутатор возьмёт fields предка (с его статическими свойствами), расширит его своими и уже полный список скопирует в this.Class.

Мутатор bind

Мутатор bind отвечает за связывание методов с контекстом.

Здесь уже не обойдёшься одним processClass(), требуется ещё и processInstance().

'bind': {
    'regexp': /^on[A-Z_]/,
    'bindvar': "__bind",
    'processClass': function (props) {
        var names = this.getMethodsNames(props),
            fields = this.fields,
            i,
            len,
            name,
            fn;
        for (i = 0, len = names.length; i < len; i += 1) {
            name = names[i];
            fn = props[name];
            if (typeof fn === "function") {
                delete props[name];
                fields[name] = fn;
            }
        }
    },
    'processInstance': function (instance) {
        var bind = go.Lang.bind,
            fields = this.fields,
            original,
            binded,
            k;
        for (k in fields) {
            if (fields.hasOwnProperty(k)) {
                original = fields[k];
                binded   = bind(original, instance);
                binded.__original = original;
                instance[k] = binded;
            }
        }
    },
    'getMethod': function (name, instance) {
        if (this.fields.hasOwnProperty(name)) {
            return go.Lang.bind(this.fields[name], instance);
        }
        return undefined;
    },
    'getMethodsNames': function (props) {
        var names,
            k,
            reg = this.regexp;
        if (props.hasOwnProperty(this.bindvar)) {
            names = props[this.bindvar];
            if (!names) {
                names = [];
            }
            delete props[this.bindvars];
        } else {
            names = [];
            for (k in props) {
                if (props.hasOwnProperty(k)) {
                    if (typeof props[k] === "function") {
                        if (reg.test(k)) {
                            names.push(k);
                        }
                    }
                }
            }
        }
        return names;
    }
}

В момент создания класса, processClass() получает список всех методов, которые следует связать с контектом (через getMethodNames()). Он обходит их всех и сохраняет в fields, удаляя из прототипа.

В момент создания каждого объекта для него вызывается processInstance() и он заполняется сохранёнными в fields методами (которые налету связываются с этим объектом).

За счёт копирования fileds, как и в случае с мутатором static, bind-методы наследуются.

getMethod()

Если мутатор сохраняет у себя и удаляет из прототипа какие-то методы (как мутатор bind) он должен определить метод getMethod().

Он используется при доступе к родительской реализации метода.

ParentClass.__method(this, 'onClick', e); // Вызов родительской реализации onClick

Если метод onClick не будет найден в прототипе ParentClass (а он удалён оттуда bind'ом), то будут опрошены все мутаторы, на предмет, не сохранили ли они этот метод у себя.

Если мутатор сохранил метод, то должен вернуть его (хотя может и изменить). Если у мутатора такого метода нет, должен вернуть undefined.

Наследование мутаторов

Если мутатор был уже определён в родительских классах, то он расширяется:

var TestClass = go.Class({
    '__mutators': {
        'bind': {
            'regexp': /^.+$/
        }
    }
});

Для мутатора bind переопределено свойство regexp. Оно использовалось для указания того, какие методы биндить (те, что начниаются с "on"). Теперь будет биндится всё подряд.

Множественное наследование

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

Удаление мутаторов

Чтобы удалить, мутатор нужно переопределить на null.

var TestClass = go.Class({
    '__mutators': {
        'static': null
    }
});

Всё, теперь для TestClass и его потомков статических методов не будет.

Clone this wiki locally