5 сент. 2008 г.

Еще раз о наследовании в JavaScript

Всегда смущала обычная реализации наследования в JavaScript, а в особенности вызов метод базового класса из метода потомка. Здесь и в дальнейшем я буду использовать термин «класс» из классического ООП, хотя в JavaScript заложена немного другая концепция.

Допустим, что у нас есть иерархия классов вида A → B → C и в методе наследников нам нужно вызывать тот же метод базового класса вверх по иерархии. Обычно это выглядит примерно так (вариантов может быть несколько, но смысл не меняется):

// описание методов класса A ... method : function(param1, param2) { // действия класса A } ... // описание методов класса B ... method : function(param1, param2) { // действия базового класса A A.prototype.method.call(this, param1, param2); // действия класса B } ... // описание методов класса С ... method : function(param1, param2) { // действия базового класса B B.prototype.method.call(this, param1, param2); // действия класса C } ...

Таким образом, для вызова метода базового класса приходится сооружать такую неуклюжую конструкцию, где каждый раз упоминается имя базового класса и имя метода, где приходится вызывать call, а в параметрах передавать this. Вообщем, мягко говоря, не очень красиво. Ведь было гораздо красивее написать так:

// описание методов класса B ... method : function(param1, param2) { // действия базового класса A this.__base(param1, param2); // действия класса B } ... // описание методов класса С ... method : function(param1, param2) { // действия базового класса B this.__base(param1, param2); // действия класса C } ...

Возможно ли такое в JavaScript? Оказывается, возможно.

Читая блог Dean Edwards’а, я наткнулся на интересную идею реализации подобного функционала — при наследовании не просто записывать в прототип нового класса перекрываемый метод, а заворачивать его в метод-обертку, которая будет перед вызовом метода заменять this.__base на необходимый метод базового класса, а после вызова восстанавливать (потому что возможно ситуация когда перекрываемый метод вызывает другие методы, которые также могут вызывать одноименные методы базовых классов). Тоже самое можно сделать и для конструктора.

В итоге, наследование у меня происходит сейчас так:

var SubClass = BaseClass.inheritTo( { // если нужно, перекрываем конструктор __constructor : function(param) { // если нужно, вызываем конструктор базового класса this.__base(param); // и делаем что-нибудь еще }, // перекрываемый метод method : function(param) { // если нужно, вызываем метод method базового класса this.__base(param); // и делаем что-нибудь еще } }, { // если необходимо, тут перекрываем статические свойства и методы } );

Для единообразия базовый класс создается так (наследуется от абстрактного класса Abstract):

var Class = Abstract.inheritTo( { // конструктор __constructor : function(param) { }, // обычный метод method : function(param) { } }, { // если необходимо, тут создаем статические свойства и методы } );

Работоспособность этого способа была проверена на иерархии классов ZForms (около 30 классов). После рефакторинга код стал намного стройнее, короче и понятнее.