Popular Posts

вторник, 28 февраля 2012 г.

PrototypeJS Классы, Наследование и Модули.

Привет!
Сегодня важная тема, я бы сказал -- это наследование и классы и объекты в prototype, ну также всё, что будет показано относится в равной мере и ко всему javascript.

Проблема

Как всегда сначала описание проблемы. Предположим у меня есть мебель: шкаф и диван. Ну да немного, но для задачи достаточно. Как это выглядит со стороны объектов? Понятно, что и то, и то -- мебель, ведь так? Но на диване я сижу, лежу, и так далее, а в шкафу куча дверек, в шкаф я складываю одежду\посуду\да_что_угодно. Но также есть такие общие вещи как цвет. Вот.
Теперь непосредственно проблема, по идее диван и шкаф виды мебели, но диван и шкаф могут иметь разные свойста (для сиденье? сколько двёрок? и так далее). Как же это описать?
Вот тут-то и появляются так называемые MixIn, или модули, которые могут быть в новинку для javascript программистов, но отнюдь не новы для тех, кто пишет на ruby. Но благодаря, природе javascript в нём можно спокойно делать модули и разбавлять объекты. Я написал много слов, теперь напишу код.
И так. Сначала практика создания классов в prototypejs
Тут просто:
var SomeClass = Class.create({
    initialize: function(){}
});
Как просто, да? Сначала делаем класс Class.create, в нём нужна функция конструктор (но она не обязательна) initialize, что сродни ruby методу с таким же названием. Это функция будет инициализироваться при создании объекта. Всё просто.
Всё теперь пишу нужный код:
var Furniture = Class.create({
    initialize: function(title, price)
    {
      this.title = title;
      this.price = price;
    },
    toString: function()
    {
          return this.title + ": " + this.price + "$"
    }
    });
Две функции, инициализации, и вторая функция для вывода информации о мебели.
Этот класс в java я бы сделал абстрактным, потому что просто мебели как бы не существует, это абстаркция, мебель это всегда что-то -- стул, стол, шкаф...

Теперь пишем классы для Шкафа и Дивана:
var Sofa = Class.create(Furniture, {
    initialize: function($super, title, price)
    {
      $super(title, price);
    }
});
var Cupboard = Class.create(Furniture, {
    
    initialize: function($super, title, price)
    {
      $super(title, price);
    }
});
Как вы, надеюсь, поняли, здесь мы наследуем от мебели (Furniture) с помощью Class.create(ExtObj, {}), где ExtObj -- это "родитель". В инициализаторе вызываем $super, это
заставляет проинициализировать код функции родителя. Про $super, читайте в конце поста.

Ok, я это сделал, теперь время подмешать модули.
Пусть есть такие свойста: цвет, можно ли сидеть, сколько дверек в мебели.
//для цвета
var Color = {
      set_color: function(color)
      {
        this.color = color;
      },
      get_color: function()
      {
        return this.color;
      }
};
//Я могу сесть на эту мебель?
var ForSeating = {
    for_seating: function()
    {
      return true;
    }
};
//Сколько дверек в этой мебели?
var ManyDoors = {
    set_doors: function(doors)
    {
        his.doors = doors;
    },
       how_many: function()
    {
      return this.doors;
    }
};
Ok, круто, как видим все эти модули просты Object\ы  в javascript.
Теперь самое интересное и простое, как подмешать? Для этого есть методы Object, который расширяет Prototype -- addMethods()
Получаем:
//Для дивана модуль который говорит, что можно сидеть и модуль цвета
Sofa.addMethods(ForSeating);
Sofa.addMethods(Color);
//Для шкафа цвет и дверки.


Cupboard.addMethods(Color);
Cupboard.addMethods(ManyDoors);
Что получим?

Вот результаты:
Сначала создаём новые:
var sofa = new Sofa('Cool Sofa', 10);
var cupboard = new Cupboard('My Cupboard', 12);


//Устанавливаем цвета и двери для шкафа
sofa.set_color('red');

cupboard.set_color('black');
cupboard.set_doors(3);

//А вот результат уже
sofa.get_color(); // => 'red'
sofa.for_seating(); // => true
cupboard.get_color(); // => 'black'
cupboard.how_many(); // =>  3
Если мы вызовем для шкафа метод, который скажет можно ли на нём сидеть? то получим



Вот так.

Совсем маленький рефакторинг.

Как мы видили, Модуль Color добавляется и в Sofa и в Cupboard, поэтому лучше "подмешать" в родительский класс:

Furniture.addMethods(Color);

Теперь методы этого модуля будут доступны в обоих объектах.

Для чего всё это нужно?

Это позволяет вынести общие методы для разных объектов, что упростит жизнь, ведь кода нужно меньше, вы не будете повторять себя.
Сделает понятнее программу.
Какие возможные траблы?
Возможно затереть методы, очень часто есть методы set()\get(), которые могут быть реализованы для разных модулей, в итоге останется только один, как горец))). Также сложность следить за программой, у вас в модуле появится ссылка this, на объкт + куча свойств объекта, не сразу понятно как этим пользоваться, но это просто, так же в связи с этим можно случайно переопределить свойство, поэтому рекомендую не полениться и написать get_property()\set_property(), так лучше.

Я написал много слов об недостатках, но это для того, чтобы предупредить о сложности, на самом деле плюсов больше.

Примечание: Наследование PrototypeJS

Снова немного кода, для пояснения некоторых вещей.  Есть несколько классов:
var A = Class.create({
    say: function()
    {
      alert('A');
    },
    method: function()
    {
      alert('method A');
    }
    });
    var B = Class.create(A, {
    say: function()
    {
      alert('B');
    }
    });
    var C = Class.create(A, {
    say: function($super)
   {
      $super();
      alert('C');
    },
    abc: function()
    {
      alert('abc');
    }
    });
   
    var a = new A();
    var b = new B();
    var c = new C();
    a.say(); // => A
    b.say(); // => B
    c.say(); // => A, C
    c.method(); // => method A
Здесь видим наследование B и C наследует A. Но в B мы пишем свой метод say(). а в C вызываем сначала родительский $super() -- это пригодится, когда нужно установить некоторые свойста, которые определяются в родительском, например при инициализации. Также видим, что метод method() из A доступен и в C дочернем классе.  Доки






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

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