Туторіал - Prototype: Визначення Класів та Наслідування

Визначення Класів та Наслідування

У попередніх версіях Prototype, framework забезпечував створення класу: метод Class.create(). Досі єдиною можливістю визначення класів таким шляхом полягала в тому, що конструктор автоматично викликав метод initialize. Тепер Prototype 1.6.0 постачається з підтримкою наслідування через модуль Class, який взято кількома етапами пізніше за останню версію; можна зробити цінніші класи в нашому коді з більшою легкістю, ніж раніше.

Наріжним каменем у створенні класу Prototype і досі залишається метод Class.create(). У новій версії framework-у наш основний код класу працюватиме як і раніше, проте тепер не доведеться прямо працювати з об'єктом прототипів чи використовувати певний Object.extend() для копіювання властивостей.

Приклад

Давайте порівняємо старий і новий способи визначення класів та наслідування в Prototype:

/ **obsolete syntax** /

var Person = Class.create();
Person.prototype = {
initialize: function(name) {
this.name = name;
},
say: function(message) {
return this.name + ': ' + message;
}
};

var guy = new Person('Miro');
guy.say('hi');
// -> "Miro: hi"

var Pirate = Class.create();
// inherit from Person class:
Pirate.prototype = Object.extend(new Person(), {
// redefine the speak method
say: function(message) {
return this.name + ': ' + message + ', yarr!';
}
});

var john = new Pirate('Long John');
john.say('ahoy matey');
// -> "Long John: ahoy matey, yarr!"

Використовуючи Object.extend, спостерігаємо прямий взаємозв'язок з класом прототипів та зовсім незручним способом наслідуванням. Крім того, для Pirate переосмислення say() методу з Person, не існує жодного способу виклику перевизначеного методу (подібно до того, як можна робити в мовах програмування, що підтримують наслідування класів).

Це змінилося в кращу сторону. Порівняйте вище з настуним:

/ **new, preferred syntax** /

// properties are directly passed to `create` method
var Person = Class.create({
initialize: function(name) {
this.name = name;
},
say: function(message) {
return this.name + ': ' + message;
}
});

// when subclassing, specify the class you want to inherit from
var Pirate = Class.create(Person, {
// redefine the speak method
say: function($super, message) {
return $super(message) + ', yarr!';
}
});

var john = new Pirate('Long John');
john.say('ahoy matey');
// -> "Long John: ahoy matey, yarr!"

Можна помітити, як визначення є коротшими для обидвох: класу та суперкласу; оскільки більше не потрібно безпосередньо змінювати код програми їхніх прототипів. Також продемонстровано ще одну особливість: «supercall», або викликаючи перевизначений метод (виконано з спеціальним ключовим словом super в мові Ruby) в Pirate#say.

Як Поєднювати в Модулях

Дотепер було представлено загальний спосіб виклику Class.create.

var Pirate = Class.create(Person, { /* instance methods */ });

Але, в дійсності, Class.create займає довільне число аргументів. Перший — якщо це інший клас — описує з якого класу повинен наслідуватися новий. Всі інші аргументи будуть додані як методи екземпляру; по суті вони є послідовними викликами до addMethods (див. нижче). Це може бути зручно використано для перемішування в модулях:

// define a module
var Vulnerable = {
wound: function(hp) {
this.health -= hp;
if (this.health < 0) this.kill();
},
kill: function() {
this.dead = true;
}
};

// the first argument isn't a class object, so there is no inheritance ...
// simply mix in all the arguments as methods:
var Person = Class.create(Vulnerable, {
initialize: function() {
this.health = 100;
this.dead = false;
}
});

var bruce = new Person;
bruce.wound(55);
bruce.health; //-> 45

$super Аргумент у Методі Визначення

Коли ви перевизначате метод в підкласі, проте й надалі бажате мати можливість викликати початковий метод, тоді необхідно вказівник (reference) на нього. Для одержання такого вказівника слід визначити ці методи з додатковим аргументом на початку: $super. Prototype виявить його і зробить перевизначення методу, який є доступним завдяки аргументові. Проте, з іншого боку, метод Pirate#say досі очікує на єдиний аргумент; ця деталь реалізації не впливає на те, як ваш код взаємодіяє з об'єктами.

Види Наслідування в Мовах Програмування

В цілому ми виокремлюємо класове та прототипне наслідування, останнє зорієнтоване на JavaScript. Хоча майбутня JavaScript 2.0 і буде підтримувати певні визначення класу, але поточна версія мови JavaScript, яка виконується в сучасних браузерах, є обмеженою щодо прототипного наслідування.

Прототипне наслідування, безумовно, є дуже корисною особливістю мови, проте часто є надто багатослівною при фактичному створенні наших об'єктів. Ось чому ми, по суті, моделюємо класове наслідування (наприклад, на мові Ruby) через прототипне наслідування. Це має певні наслідки. Наприклад, в PHP можна визначити початкові значення для змінних екземпляру:

class Logger {
public $log = array();

function write($message) {
$this->log[] = $message;
}
}

$logger = new Logger;
$logger->write('foo');
$logger->write('bar');
$logger->log; // -> ['foo', 'bar']

Ми можемо спробувати зробити те ж саме в Prototype:

var Logger = Class.create({
initialize: function() { },
log: [],
write: function(message) {
this.log.push(message);
}
});

var logger = new Logger;
logger.log; // -> []
logger.write('foo');
logger.write('bar');
logger.log; // -> ['foo', 'bar']

Це працює. Проте що буде, якщо ми робимо ще один екземпляр Logger?

var logger2 = new Logger;
logger2.log; // -> ['foo', 'bar']

// ... hey, the log should have been empty!

Можна побачити що, хоча й очікувались деякі порожні масиви у нових екземплярах, у нас є той же масив, як і в попередньому logger-і. Насправді, всі об'єкти logger-а будуть розділені одним і тим же масивом об'єкту, оскільки він копіюється вказівником, а не значенням. Правильний шлях полягає в тому, щоб ініціалізувати наші екземпляри до значеннь за замовчуванням :

var Logger = Class.create({
initialize: function() {
// this is the right way to do it:
this.log = [];
},
write: function(message) {
this.log.push(message);
}
});

Визначення Методів Класу

Не існує жодної спеціальної підтримки для методів класу в Prototype 1.6.0. Просто слід визначити їх в даних існуючих класах:

Pirate.allHandsOnDeck = function(n) {
var voices = [];
n.times(function(i) {
voices.push(new Pirate('Sea dog').say(i + 1));
});
return voices;
}

Pirate.allHandsOnDeck(3);
// -> ["Sea dog: 1, yarr!", "Sea dog: 2, yarr!", "Sea dog: 3, yarr!"]

Якщо потрібно визначити декілька відразу — просто використовуйте Object.extend як і раніше:

Object.extend(Pirate, {
song: function(pirates) { ... },
sail: function(crew) { ... },
booze: ['grog', 'rum']
});

Якщо наслідувати від Pirate, то методи класу не успадковуються. Цю можливість можливо включать в наступні версії Prototype. До того часу, слід копіювати методи вручну, але не так:

var Captain = Class.create(Pirate, {});
// this is wrong!
Object.extend(Captain, Pirate);

Конструктори класу є об'єктами Function і буде розгардіяш, якщо просто копіювати все з одного в інший. У кращому випадку ви досі будете закінчувати перевизначення властивостей subclasses та superclass для Captain, що не є добре.

Спеціальні Властивості Класу

Prototype 1.6.0 визначає дві спеціальні властивості класу: subclasses і superclass. Їхні назви є самодокументованими: вони зберігають вказівники відповідно до підкласу чи суперкласу даного класу.

.superclass
// -> null
Person.subclasses.length
// -> 1
Person.subclasses.first() == Pirate
// -> true
Pirate.superclass == Person
// -> true
Captain.superclass == Pirate
// -> true
Captain.superclass == Person
// -> false

Тут ці властивості є для простого самоаналізу класу.

Додавання Методів на льоту з Class#addMethods()

_Class#addMethods був названий Class.extend в Prototype 1.6 RC0.

Будь ласка, оновіть відповідно ваш код._

Уявіть, що у вас вже є визначений клас, до якого треба включити додаткові методи. Звичайно, ви захочете, щоб ці методи відразу були доступними підкласам та всім існуючим екземплярам в пам'яті! Це досягається шляхом впровадження властивості у ланцюжок прототипу, а найбезпечніший спосіб, щоб зробити це з Prototype, — використання Class#addMethods:

var john = new Pirate('Long John');
john.sleep();
// -> ERROR: sleep is not a method

// every person should be able to sleep, not just pirates!
Person.addMethods({
sleep: function() {
return this.say('ZzZ');
}
});

john.sleep();
// -> "Long John: ZzZ, yarr!"

Метод sleep став моментально доступним не тільки для нових екземплярів Person, але і для його підкласів та наразі існуючих в пам'яті екземплярів.

Це переклад з офіційної документації.

© 2009 - 2020, Розробка - соціальна ІТ спільнота.
Контакти: info@rozrobka.com
Правила користування