MooTools: Класи

Вибачаюсь за доволі велику затримку з постом. Це зв`язано з тим, що я недавно почав глубоко і досконало вивчати JavaScript, зі всіма "вузькими моментами" і т.п. Продовжую серію статтей про мій улюблений JavaScript фреймворк MooTools. Сьогодні мова піде про використання класів.

Я не буду вдаватися в подробиці реалізації системи класів, про це, скоріш за все, мова піде пізніше, при глубокому вивчені фреймворку. І так, що таке клас ? Клас - це шаблон, який описує властивості і поведінку майбутнього об'єкта. На відміну від інших ОО мов програмування, JavaScript нативно не підтримує так званих парадигм ООП, тим не меньше, гнучкість цієї мови дозволила їх в більшій чи меньшій мірі реалізувати на рівні фреймворку. MooTools не перший і не єдиний фреймворк, який реалізовує це, але можливості які він надає, порівняно з іншими, дійсно вражають.

Почнемо з самого початку, з створення шаблону/класу. Для створення свого класу нам достатньо написати:
var myClass = new Class({});
Але цей код не несе нічого корисного. Щоб зробити наш клас більш корисним, потрібно в нього додати дані і логіку, властивості і поведінку. Давайте це зробимо на класичному прикладі моделювання тварин з допомогою ООП:
var Animal = new Class({
    name: null,
    initialize: function(name) {
        this.name = name;
    },
    getName: function() {
        return this.name;
    }
});

var animal = new Animal('Animal Name');
animal.getName(); // звичайно ж поверне "Animal Name"
Ми створили свій клас Animal, в якого є поле/властивість name, метод getName, який поцертає значення name, і функція initialize, як виконує роль конструктора. Для тих, хто не в курсі, що таке контструктор, коротке пояснення: конструктор - це функція яка автоматично викликається після створення об'єкта, щоб ініціалізувати його (себто задати початкові параметри), ну або для інших дій, відомих тільки конкретному програмісту. Наш конструктор приймає один параметер, ім'я, і записує його в відповідне поле.

В нас вже є загальний клас Тварина, пора створити кілька конкретних видів (для чого ми використаємо наслідування):
var Cat = new Class({
    Extends: Animal,

    meow: function() {
        console.log('meow');
    }
});

var cat = new Cat('Murka');
cat.getName(); // поверне "Murka"
cat.meow(); // виведе в консоль файрбага "meow" (мяу)
Як бачимо, при створенні класу Cat було використано властивість Extends. Extends в даному контексті виступає так званим мутатором. Мутатори - це поля, які використовуються MooTools'ом для перед-ініціалізації об'єкта, тобто фреймворк виконує ще деякі дії (в тому числі з викорастанням даних з полів-мутаторів) ще до виклику нашого конструктора (initialize). Мутатор Extends вказує на то, які класи наслідує наш клас, в даному випадку це Animal. Якщо ви хочите щоб ваш клас наслідував кілька класів, вказуйте їх списом-масивом, ось так:
var Cat = new Class({
    Extends: [Animal, AnotherClass],

    meow: function() {
        console.log('meow');
    }
});
Мутатори появилися не так давно, раніше використовувся (і ним можна досі користуватись) дещо інший спосіб:
var Cat = new Class({
    meow: function() {
        console.log('meow');
    }
});
Cat.extend(Animal);

var cat = new Cat('Murka');
cat.getName(); // поверне "Murka"
cat.meow(); // виведе в консоль файрбага "meow" (мяу)
Extends це звичайно не єдиний мутатор (більше того, можна створювати свої мутатори, про це поговоримо якось іншим разом), ще один з найчастіше використовуваних - Implements. Різниця між цми двома мутаторами доволі розпливчаста, і дуже важливо її зрозуміти. Спробую її пояснити саме на встроєних методах extend і implement.

Extend означає "розширення", цей метод додає функціонал до класу а не до об'єкта, створеного цим класом, implement ж навпаки. На прикладі, напевне, буде ясніше:
var Barn = new Class();

Barn.implement({
    instanceMethod: function(){
        return 'From an instance!';
    }
});

Barn.extend({
    classMethod: function(){
        return 'From the class itself!';
    }
});

var myBarn = new Barn();

typeof(myBarn.instanceMethod); // повертає 'function'
myBarn.instanceMethod(); // повертає 'From an instance!'
typeof(myBarn.classMethod); // повертає 'undefined'

typeof(Barn.classMethod); // повертає 'function'
Barn.classMethod(); // повертає 'From the class itself!'
typeof(Barn.instanceMethod); // повертає 'undefined'
Думаю з прикладу стало все зрозуміло. Створюючи наш клас Кішки ми використали extend для того щоб розширити наш клас іншим класом. Це і є MooTools наслідуванням.

Якщо ми вже згадали про мутатори, то, думаю, доречно би було згадати про корисні "мутування" створюваних нами класів.
var myClass = new Class({
    Implements: [Options, Log, Events],

    options: {
        variable: 'default value',
        variable2: 'default value for variable 2'
    },

    initialize: function(config) {
        this.setOptions(config);
        this.log(this.options);
    },

    getVariable: function() {
        this.fireEvent('getVariable');
        return this.options.variable;
    }
});

var my = new myClass({
    variable: 'new value'
});
my.addEvent('getVariable', function() {
    this.log('getVariable called');
});
my.getVariable(); // поверне "new value"
Options використовується для того, щоб дати можливість зручно налаштовувати об'єкти класів. Як бачите, ми в конструкторі приймаємо конфігураційний об'єкт, і передаємо його методу setOptions, який "накладає" новий конфіг на дефолтний (той що по замовчуванню був прописаний в класі).

Log був створений для звичайного логування. Працює наступним чином: якщо інснує файрбаговська консоль, тоді пише в неї, якшо ні, то в внутрішній масив повідомлень. В прикладі, в конструкторі ми виводимі конфігураційний масив, який получився після обробки його методом setOptions.

Events додают в клас, коли в класі потрібний функціонал подій (хто програмує на яваскрипті, точно знає про що я). В прикладі в методі getVariable генерується однойменне повідомлення. Як ми бачимо, Events також додає метод addEvent, який дозволяє "підписатись" на потрібну нам подію.

Зверніть увагу: ми використовуємо мутатор Implements а не Extends, це тому, що ми не розширюємо (тут немає наслідування) а доповнюємо наші класи.

Далі ми попробуємо захистити наші поля і методи від зовнішнього втручання. Для цього в JavaScript'і використовують так звані closures:
(function(){
    var secret = 'I like bacon.';

    this.Secretive = new Class({
        getSecret: function(){
            return secret;
        },
        setSecret: function(newSecret){
            secret = newSecret;
            return this;
        }
    });
})();

var secret = new Secretive();

typeof(secret.secret); // поверне 'undefined'
secret.getSecret(); // поверне 'I like bacon.'

secret.setSecret('I like bacon too!');
secret.getSecret(); // поверне 'I like bacon too!'
Починаючи з MooTools версії 1.2.2 появився інший метод захиститися:
var Secretive = new Class({
    secretFunction: (function(){
        return 'The cake is a lie.';
    }).protect(), // !!!

    tellSecret: function(){
        return this.secretFunction();
    }
});

var secrets = new Secretive();
secrets.tellSecret(); // поверне 'The cake is a lie.'

typeof(secrets.secretFunction); // також поверне 'function'
secrets.secretFunction(); // але викликати метод ми не зможемо
Як бачите, тут був використаний метод функції (так, функції також можуть мати свої методи, так як вони також є об'єктами) protect.
Також повідомляється, що в майбутній версії 2.0 фреймворку буде можливість використовувати так званий "мутатор захисту". Ось приклад:
var Secretive = new Class({
    'protected secretFunction': function(){
        return 'The cake is a lie.';
    },
    tellSecret: function(){
        return this.secretFunction();
    }
});
Ну і під кінець хочу згадати про новий метод створення класів (появився в 1.2.2):
var myClass = new Class(function() {
    // це конструктор
});

коментарі:

Ян Лі 19.10.2009 16:56
На відміну від інших ОО мов програмування, JavaScript нативно не підтримує так званих парадигм ООП.
JavaScript підтримує прототипи і у деякому сенсі є значно більш OO-мовою, ніж C++.

Звичайно, ті, хто звик до C++, і на JavaScript програмуватимуть як на C++.
slik 19.10.2009 18:47
Я мав на увазі наслідування, поліморфізм і інкапсуляцію

додати коментар: