воскресенье, 14 июня 2015 г.

ООП и JavaScript

Последние несколько недель свободного времени мне пришлось потратить на осознание одного простого факта: "Не все объекты одинаково полезны."

Идея была проста: "А давайте сделаем js игру-тамагочу?" Пусть это будет... хомяк?..

И сегодня расскажу вам о том, как си-шарпер пытается познать ООП в малознакомом ему яваскрипте.

Если не художественно, то материалы, которые спасли мою психику находятся здесь: http://javascript.ru/tutorial/object .

Тем не менее, здесь будет еще и краткий конспект и без того краткого мануала.

Итак. Задача:
Создать класс хомяка, содержащий в себе некие данные о хомяке и методы манипуляции с этими данными, для последующего обращения к этому хомяку классу из игрового процесса.

Поскольку ТЗ мое весьма неподробно в систему надо закладывать максимальную гибкость.

Если бы я писала на шарпах все было бы просто. Но не вопрос думаю я, я так часто слышу, что js это ооп объект. Есть столько расширений, библиотек, фреймворков... наверное и я смогу это освоить.

Поэтому... я открываю гугл и забиваю что-то вроде "классы в js". И сразу узнаю, что я могу создать объект конструкцией вроде:

window.Hamster = {

    status = "good",    health = 50,    age = 0
}

Что, в целом, неплохо.
Я могу добавить методы:

window.Hamster = {

    ... ,
    EatSmth : function(n) { this.health =+ this.health + n; },
    CheckHealth : function() { if (this.health < 50) this.status = "ill"; }
}

И вот тут я думаю, а что бы мне не вызвать функцию проверки здоровья собственно сразу при изменении здоровья?.. И... и тут начинаются проблемы. Потому, что как это не удивительно для меня, старого си-шарпера, код...

this.CheckHealth();

...удивительным образом не работает. 

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

Примечание: зато работает:

EatSmth : function(n) 

    this.health =+ this.health + n; 
    window.Hamster.CheckHealth();
},

В общем, один статический класс с кучей статических методов: никаких экземпляров, доступ к объекту от окна и невозможность адекватной работы методами. Если подходить к вопросу более подковано, то мы совершенно неожиданно для себя имеем реализацию паттерна Одиночка - он же Singleton. Что, в целом, круто (взял, что-то накодил, бац, реализовал паттерн...), но совершенно не о том, что нам надо и потому неприемлемо.

Что же делать мне, при желании реализовать свой объектный замысел?

Факт первый, чтобы создавать одинаковые структуры нужно создать функцию. Любая функция в js может создать объект, если вызывается через  new.  Полями объекта являются все определенные в функции через this поля.
Чтобы создать тип объекта нам нужна своего рода порождающая функция.

function Hamster() { 
    this.status = "good";    this.health = 50;
    this.EatSmth = function(n) 
    { 
       this.health =+ this.health + n; 
       this.CheckHealth();
    }
    this.CheckHealth = function() {
       if (this.health < 50) this.status = "ill"; 
    }
}

var MyNewHam = new Hamster();
MyNewHam.EatSmth(5);

И, вуаля, я могу иметь сколько угодно хомяков c какой мне угодно сложностью цепочек вызова.

И хотя задача решена... Но это не самое интересное, "что я узнал сегодня". Тем более, что углубляясь в задачу у меня возник вопрос наследования

Факт второй.
В js нет разницы между классом и переменной (как можно было уже заметить на самом первом примере, который оказался реализацией singleton). По крайней мере в явном виде, как я привыкла в C#.

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

function BaseParam() { 
    this.status = "good";    this.value = 50;
}

И унаследовать от него свои параметры, которые уже буду вставлять в хомяка. Например, здоровье. И, собственно, для таких случаев, когда очень хочется вставить в свою архитектуру немножко наследования в js есть прототипирование! У нас нет возможности создавать классы в явном виде... зато мы можем наследоваться от другой переменной. Для этого внутри каждой функции есть свойство prototype, через которое происходит наследование создаваемыми функцией объектами. По умолчанию, все создаваемые объекты наследуются от класса Object.

Вообще говоря, каждый раз когда мы создаем новый объект-структуру: var a = {}  неявно вызывается функция конструктор  var a = new Object();

Она же, как это как раз привычно и понятно для меня, вызывается как конструктор базового класса в C#, вызывается и возвращает свой объект как прототип при вызове любой функции-конструктора.

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

function HealthParam(current_health) {
    this.value = current_health;

    this.EatSmth = function(n) 
    { 
       this.value =+ this.value + n; 
       this.CheckHealth();
    }

    this.CheckHealth = function() {
       if (this.value < 50) this.status = "ill"; 
    }
}
HealthParam.prototype = new BaseParam();

function Hamster() { 
    this.Health = new HealthParam(50);
    this.Strength = new StrengthParam(50);
    this.EatSmth = function(n)
    {
        this.Health.EatSmth(n);
        this.Strength,EatSmth(n);
        //все что еще мне нужно по событию кормления хомяка
    }
}

Красиво, аккуратно и... и никаких вызовов "от окна". И заложенная масштабированность на все, что мне пока может придти в голову.

В попытке создать хомяка использованы:
1. http://javascript.ru/tutorial/object
2. Хабрахабр: Использование паттернов проектирования в javaScript: Порождающие паттерны

А также для более хитрых и англоязычных читателей:
3. http://shichuan.github.io/javascript-patterns/
4. http://addyosmani.com/resources/essentialjsdesignpatterns/book/

Комментариев нет:

Отправить комментарий