"Классический" ООП в JavaScript
Несколько раз начинал писать этот пост, но в итоге решил не распинаться, а спросить коротко и ясно.
Используете ли вы эмуляцию "классического" ООП (с классами и наследованием на классах) в своих проектах, или пользуетесь конструкторами и наследованием на прототипах? Или вообще не пользуетесь наследованием? |
Зависит от используемого фреймворка. В данный момент в основном Ext, соответственно и наследование через Ext.extend. Если что-то делается без фреймворка, то, как правило, и наследование не особо нужно.
|
Kolyaj,
Другими словами, если бы ты решал задачу уровня ExtJS, но без фреймворка, то реализовал бы классическую модель ООП? |
Цитата:
Цитата:
|
Чтобы более точно представить картину, надо понять, что, если говорить о разнице в подходах, то нужно говорить больше не о "класс vs. прототип" и даже не о "наследование vs. делегация", а в большей мере о "статика vs. динамика".
Я практически всегда для примера привожу классовую модель Python'a, чтобы показать, что разницы между "динамическим ООП на классах" и "ООП на прототипах" практически нет (если нужно будет привести примеры и показать сходство, скажите). Что же касается статической классовой ООП модели (aka Java или C++), то ее просто физически нельзя сделать в JS (даже эмуляцию). Во сколько бы обертор мы не обернули бы конструкторы, внутри все равно будет prototype-base inheritance. Единственное, для чего можно использовать обертки - это улучшение code reuse - вынос повторяющегося блока наследования (обеспечение связки прототипов) в обертку. Как потом эту обертку называют - "Class", "Extend", "Inherit" и т.д. - не суть важно, главное, чтобы человек понимал, что он делает. Другой вопрос, когда начинается непонимание технологии ввиду того, что человек не имеет достаточного объема информации и может доказывать то, что к JS не имеет никакого отношения. Часто причиной этому могут быть статьи с громкими заголовками типа "Наследование на классах в JS", которые подсознательно могут откладываться в умах людей, который пока поверхностно разбираются в JS, как "доведем до ума этот бесклассовый недоязык". Это уже неправильно. А в целом - никто не ограничивает делать обертки (для улучшения code reuse'a) и называть как вздумается. У меня в одном проекте был виджет-фреймворк написан, где было построено наследование виджетов (aka Widget -> WidgetPanel -> WidgetWindow -> WidgetDialog и т.д.). Для чего я использовал там обертку? - Чтобы вынести в нее блок кода, обеспечивающий связку прототипов. Все. Объективно, это единственная причина, т.к. вся эта мишура с оберткой, могла бы быть описана и без нее - и без потери какой-либо читабельности и функциональности. Но мне было удобней с обреткой. Поэтому, если анализировать различия, то, повторю, в большей мере надо говорить о "статика vs. динамика"; пара же "прототип vs. класс" очень больших отличий не покажет. |
Kolyaj,
Цитата:
Цитата:
|
Цитата:
|
Цитата:
|
Dmitry A. Soshnikov,
ну я в рамках предложенного определения Цитата:
|
Dmitry A. Soshnikov,
Спорить по вопросу статика vs. динамика в рамках JavaScript, как я понимаю, бессмысленно. Поэтому мы говорим только о динамической модели. Ясно, что реализация динамического классового ООП на JavaScript это то же прототипное ООП, вид сбоку. Практически все фреймворки стремятся к динамической классовой модели, причем это не только обертка для перелинковки прототипов, но и установка дополнительных свойств и фабрика конструкторов. Насколько это оправданно? И насколько сложнее реализация/поддержка/рефакторинг без такой обертки? Kolyaj, Цитата:
Просто я не видел пример "элегантного" наследования на прототипах, которое бы сохраняло функциональность конструктора родительского класса, выстраивала цепочку прототипов (для instanceOf) и правильно линковала constructor. И чтоб она была без прослойки. |
Цитата:
|
Цитата:
Ну вот найди сильные отличия: # python class A(object): def __init__(self): self.a = 10 // аналог на JS function A() { this.a = 10 } x = A() # py var x = new A(); // js x.a # 10, py x.a // 10, js A.b = 20 # добавим новое свойство в класс, доступное всем инстансам A.prototype.b = 20; // аналог на js x.b # 20, py x.b // 20, js // Однако, свойство "b" не является родным для объектов "x" // сделаем его родным x.b = 30 # py x.b = 30 // js x.b # 30, py x.b // 30, js // Удалим родное свойство "b" del x.b # py delete x.b // js // Снова, за счет делегации к классу (к цепи классов; в Питоне) // и к прототипу (к цепи прототипов; в JS) // доступно свойство "b" x.b # 20, py x.b // 20, js // Более того, можно расширять класс / прототип через инстанс: x.__class__.c = 30 # py x.__proto__.c = 30 // js (__proto__ - спечифично для конкретной реализации, но - не суть) x.c # 30, py x.c // 30, js // Наследование # py class B(A): def __init__(self): A.__init__(self) // js function B() { B.superclass.apply(this, arguments); } // блок, для вынесения в обертку (для улучшения code reuse, чтобы каждый раз не повторять одно и то же) // одна из реализаций var __inheritance = function () {}; __inheritance.prototype = A.prototype; B.prototype = new __inheritance(); B.prototype.constructor = B; B.superclass = A; // при этом, конструктор может издохнуть после порождения // объекта, и объект за счет своей (неявной) ссылки на прототип // будет иметь доступ к прототипу // аналогичная картина и Python'e: // сслыка на класс - "А" - может издохнуть, // но порожденный объект все еще будет иметь // доступ к классу за счет ссылки на класс, включая явную - __class__ // примеры не привожу, здесь понятно. Цитата:
|
Dmitry A. Soshnikov,
Спасибо за разъяснения. Получается, что простая реализация наследования реализуется с избыточным кодом: var __inheritance = function () {}; __inheritance.prototype = A.prototype; B.prototype = new __inheritance(); B.prototype.constructor = B; B.superclass = A; Который рациональнее прятать в отдельную функцию. Получается, что это скорее "стандартный прием", чем простое упрощение. Цитата:
Фреймворки и убирают этот момент, делая динамические классы: var myClass = new Class({ "extend": myParentClass, "constructor": function() { /* "конструктор" */ }, "property": "value" }); Я и хотел узнать, насколько такой подход необходим и насколько считается "плохим тоном", говоря о JavaScript. Получается, что такой вариант помогает убрать избыточность, которую порождает обычный подход конструктор-прототип. Хоть я и разобрался в наследовании на прототипах, но думал, что что-то упустил. Потому что не понимал, зачем во фреймворках используется "эмуляция классического ООП", а именно - вид динамического ООП на классах. |
Цитата:
Касательно же обертки - никакого плохого тона. Иначе "плохим тоном" можно было бы назвать и это: for (var k = 0; k < 3; k++) { alert(k); } У нас есть повторяющийся кусок кода, мы его выносим в цикл. Можно было бы привести пример - выносим в функцию - не суть, то же самое. Иначе: alert(0); alert(1); alert(2); Это, стало быть, не "плохой тон", т.к. мы не используем обертку для повторяющегося куска кода. Ну, да, code reuse методом copy-paste - очень "красивый" метод ;) |
Цитата:
|
Zeroglif,
Там ничего против, естественно, не происходит. Я просто хотел узнать насколько необходимо использование оберток в реализации наследования. |
Цитата:
|
Вставлю свои "пять копеек"...
Основная суть данной темы - не красивость или какая-то верность принципам классического ООП. Просто классовое наследование может существенно облегчить жизнь при реализации сложных проектов. Что я понимаю под классовым наследованием? 1. Возможность четкого определения принадлежности объекта классу (instanceOf) 2. Возможность вызова конструктора или любого метода базового класса Это то, что хочется получить. Очевидно, что в JavaScript без "обёрток" не обойтись. Ну, а чем "элегантнее" и проще это будет сделано, тем лучше. Кстати, интересно будет узнать ваше мнение, коллеги, по поводу моей "обёртки" (Классическое наследование в JavaScript). Буду благодарен за любые отзывы :yes: |
Цитата:
|
Цитата:
var DerivedClass = function() { arguments.callee.$super.call(this); }.$extends( BaseClass ).$class( function ($super) { this.someMethod = function () { return $super.someMethod.call(this); } }); Что тут сложного? Вопрос серьёзный, т.к. мне интерестно узнать мнения разных людей. |
Благополучно забыв, что в вашей статье описывается (т.е. читая вышеприведеный код без знания вашего механизма наследования), я не понимаю, что делают функции $class и самая первая.
Во-вторых, определение функций в конструкторе а-ля this.someMethod = function() {} создает для каждого объекта свой набор методов, вместо ссылок на прототип. |
Цитата:
Первая функция - конструктор класса DerivedClass (исправил название класса в предыдущем посте). Функция $class() - нужна для "финальной сборки" класса. Об этом, правда, подробнее можно узнать из статьи. Нет смысла здесь дублировать. Что касается "определение функций в конструкторе а-ля this.someMethod" - эту проблему тоже решает функция $class(). Функция someMethod создается только один раз для всего класса. P.S. Спасибо за отзыв :yes: |
Вы не поняли, я не прошу объяснять код, а говорю, что если мне придется читать подобный код, уйдет слишком много времени на осмысливание обертки.
Лично у меня сейчас модифицированный способ из Prototype, в Ext'е похожий по использованию. var A = Class.create({ init: function() { }, method1: function() { }, method2: function() { } }); var B = Class.extend(A, { init: function() { }, method3: function() { }, method4: function() { } }) |
Первое, что мне лично не нравится - Prototype (как и многие другие фрэймворки) создаёт "наследование" путём копирования свойств и методов из одного класса в другой. Полагаю instanceOf тут не работает?
Второе - а как насчет вызова конструктора и методов базового класса? Вот это на самом деле и есть главная задача. По крайней мере, моя. |
Цитата:
Цитата:
var B = Class.extend(A, { init: function() { B.superclass.init.apply(this, arguments); } }); |
Цитата:
Ну, посмотрим в Prototype (version 1.6.0.3, метод Class.create()): ... for (var i = 0; i < properties.length; i++) klass.addMethods(properties[i]); ... Хотя instanceof, надо признать, всё же работает (для вашего примера). |
Это не наследование реализуется, а новые методы навешиваются. Наследование там стандартное
klass.prototype = new subclass; Впрочем сейчас в Prototype слишком много всего непонятно для чего. |
Цитата:
|
Pavel_Volodko,
Сходу скачал только финальный вариант, сразу возник вопрос - почему передаются функции, а не заранее созданный объект-прототип? В смысле, в чём фишка? ;) upd а, идею функций понял, нужен верный $className... upd2 и не хочется копировать... |
А чем ваш способ this.method = function() {} отличается от приведенного прототайповского? (ну кроме того, что в прототайпе лучше тем, что вешается все на prototype)
|
Pavel_Volodko,
И мелкий же шрифт исходников у Вас в блоге, чуть глаза не сломал :/ |
Цитата:
p.s. эмулировать классическое наследование не сложнее чем использовать наследование, основанное на прототипах? Т.е. естественный вариант должен быть проще, по идее |
Цитата:
Цитата:
Цитата:
|
Цитата:
|
Цитата:
Фишка действительно в использовании функции вместо объекта-прототипа. Хотя, на самом деле, всё гениальное просто: для создания связной иерархии классов нужно цеплять прототипы к функциям-конструкторам. Имея только объект-прототип, мы такой возможности не имеем. Приходится извращаться со всякими __proto__, копированием методов или созданием фэйковых подклассов. А вот имея функцию-конструктор для создания объекта-прототипа всё делается элементарно. upd копировать и не обязательно. Я сам очень настороженно отношусь к решениям, которым мне не до конца понятны. Я просто поделился тем, что сделал под себя. Just FYI, как говорится. Цитата:
На мой взгляд, достаточно заглянуть в метод Class.create() от Prototype, чтобы задаться кучей вопросов. Кроме того, выражение "вешается все на prototype" мне кажется странным (хотя, думаю, я понимаю что вы имеете ввиду) и уж тем более я не считаю это лучшим решением. |
Цитата:
Цитата:
|
Цитата:
Цитата:
Насчет "естественный вариант должен быть проще, по идее" - проще не значит лучше. Я и сам за простые решения. Кстати, с этой точки зрения, моё решение абсолютно естественное, т.к. построено на элементарных возможностях прототипного наследования. |
Цитата:
По поводу instanceоf - по-подробнее плиз :stop: Что касается "цепляться за структуру, сохраняя суперы" - только там где это нужно, естественно. Цитата:
|
Цитата:
function inheritObject(object) { function Dummy(){}; Dummy.prototype = object; return new Dummy(); } Одновременно вместо Dummy для "подклассов" используется заранее подготовленная и заложенная в функцию структура. То есть, получив правильно прилинкованный объект-прототип, вы вместо: Person.prototype.x =//... Person.prototype.y =//... используете: this.x = //... this.y = //... что, в принципе, почти то же самое кроме доступа к локальным переменным функции из методов. Отсюда 2 вывода - то ли вам важны локальные переменные ($className), то ли вам не нравится "обвешивать" объект-прототип руками или через for-in... |
Цитата:
|
Часовой пояс GMT +3, время: 14:06. |