Comments 110
ES6 наступает вам на пятки.
Судя по таблицам его поддержки мы не будем им пользоваться в ближайший год. Судя по спецификации — классы ES6 — это синтаксический сахар над прототипами, и мне будет интересно сравнить их производительность со своим решением. Вообщем, пока ES6 наступит мне на пятку — я еще успею завоевать немного популярности :) И продукт развивается, так что время у меня еще есть.
Вроде же либы уже есть позволяющие использовать синтаксис ес6?
Да, есть 6to5
Спасибо за статью, сам использую что-то похожее на решение от мозиллы, что скажете об их реализации?
Спасибо за статью, сам использую что-то похожее на решение от мозиллы, что скажете об их реализации?
Когда начинал работу над ClassManager, то от Object.create сразу отказался — хотел поддерживать IE8. Спасибо что напомнили, померяю скорость, но результат мы получим тот же, так что для меня разницы нет.
Что насчет поддержки старых браузеров — то на начальной стадии она точно была, и сейчас скорее всего есть, но чтоб быть уверенным — стоит проверить заново.
Что насчет поддержки старых браузеров — то на начальной стадии она точно была, и сейчас скорее всего есть, но чтоб быть уверенным — стоит проверить заново.
Незабудьте про loose-mode. Ну и пользоваться es6 гораздо приятнее, чем любым костыльным синтаксисом.
«костыльным»? извините, но мой синтаксис не более «костыльный» чем синтаксис ES6. Когда вы видите мой класс — вы точно знаете, что будет сгенерировано.
Мой инструмент решает реальные задачи реальных людей. Если вам так важно видеть синие слова «public» «get» и «constructor» в IDE, то вам мой инструмент не нужен, пользуйтесь ES6.
Мой инструмент решает реальные задачи реальных людей. Если вам так важно видеть синие слова «public» «get» и «constructor» в IDE, то вам мой инструмент не нужен, пользуйтесь ES6.
В какой альтернативной вселенной ваш синтаксис удобнее es6 синтаксиса?
Ну и помимо удобства, конечно поддержка ide и стандартизированность.
Lava.ClassManager.define('Lava.Animal', {
Extends: 'Lava.mixin.Observable',
name: null,
toys: [],
init: function(name) {
this.name = name;
},
takeToy: function(toy) {
this.toys.push(toy)
}
});
class Animal extends Observable {
constructor(name) {
this.name = name;
this.toys = toys;
}
takeToy(toy) {
this.toys.push(toy);
}
}
Ну и помимо удобства, конечно поддержка ide и стандартизированность.
С таким дерзким настроем мало кто будет пользоваться вашим поделием.
В FF на первом месте TypeScript, потом Native, потом ClassManager.
Не совсем понятно зачем тестируется TypeScript, ведь он генерирует более-менее удобоваримый нативный javascript.
Как ваше решение работает с require.js?
Вот что мне однажды ответил mraleph по поводу
Vanilla.prototype.toString.call(this)
, когда я делал что-то подобное ;]Я думаю большая разница все-таки из-за того, что это prototype. Прочитать простое свойство это 1 mov, а прочитать prototype это куча кода
;;; @60: load-function-prototype. 0x45729a09 201 8b73ff mov esi,[ebx+0xff] 0x45729a0c 204 807e07b5 cmpb [esi+0x7],0xb5 0x45729a10 208 0f853006be11 jnz 0x5730a046 ;; deoptimization bailout 7 0x45729a16 214 f6460902 test_b [esi+0x9],0x2 0x45729a1a 218 751d jnz 249 (0x45729a39) 0x45729a1c 220 8b730f mov esi,[ebx+0xf] 0x45729a1f 223 81fea180002e cmp esi,0x2e0080a1 ;; object: 0x2e0080a1 <the hole> 0x45729a25 229 0f841b06be11 jz 0x5730a046 ;; deoptimization bailout 7 0x45729a2b 235 8b56ff mov edx,[esi+0xff] 0x45729a2e 238 807a0780 cmpb [edx+0x7],0x80 0x45729a32 242 7508 jnz 252 (0x45729a3c) 0x45729a34 244 8b760b mov esi,[esi+0xb] 0x45729a37 247 eb03 jmp 252 (0x45729a3c) 0x45729a39 249 8b760f mov esi,[esi+0xf]
В вашем бенчмарке была своя специфика (я правда уже не помню какая :)). В данном конкретном случае специфика немножко другая.
Легким движением руки
А все почему? Потому что
Кстати,
Хочется еще отметить, что замеряемый код содержит в себе один печальный антипаттерн — начальное значение
В целом, наследование в JS, особенно когда у базового «класса» много детей и часть методов ими всеми разделяется, в настоящее время это performance antipattern, потому что все разделяемые методы становятся мучительно полиморфными внутри. Поэтому в коде для которго действительно важна каждая инструкция наследования стоит избегать или насильно клонировать все методы с прототипа базового класса в прототип дочернего — причем не копировать по ссылке, а именно клонировать, например, с помощью компиляции клонов через
Если вам кажется, что тут я считерил — то напишите свои тесты, быстрее все равно не будет.
Легким движением руки
- NativeParentClass.prototype.method.apply(this);
+ NativeParentClass.prototype.method.call(this);
NativeInstance.method
разгоняется на Хроме в 20 раз см jsperf.com/liquidlava-class-system-performance/9А все почему? Потому что
f.apply(this)
Crankshaft не распознает. Он всегда умел f.apply(this, arguments)
распознавать и недавно его научили f.call(...)
. Если вызов метода родителя распознается — то все проинлайнивается и различные инварианты типа LoadFunctionPrototype
выносятся LICMом за пределы самого горячего цикла и, как следствие, на результат бенчмарка влияют мало (см IR цикла, красная полоса — индикатор вложенности циклов). Кстати,
LoadFunctionPrototype
похудел, он все еще больше чем одинокий mov
, но меньше, чем то чудовище, которое раньше генерировалось.Хочется еще отметить, что замеряемый код содержит в себе один печальный антипаттерн — начальное значение
this.counter
экземплярам потомков приходит из прототипа, из-за этого если создать два экземпляра и дергать у них по-очереди method
, то в этом самом методе возникает нездоровый полиморфизм, который может скушать на этом микробенчмарке до 50% производительности.В целом, наследование в JS, особенно когда у базового «класса» много детей и часть методов ими всеми разделяется, в настоящее время это performance antipattern, потому что все разделяемые методы становятся мучительно полиморфными внутри. Поэтому в коде для которго действительно важна каждая инструкция наследования стоит избегать или насильно клонировать все методы с прототипа базового класса в прототип дочернего — причем не копировать по ссылке, а именно клонировать, например, с помощью компиляции клонов через
Function
-конструктор.Собственно получается, что если уж делать убер-обёртку, то с полным клонированием методов и выносом свойств в конструктор. Вот тогда заживем ;]
Благодарю за консультацию, обновил тест и ссылку на главной.
Как я понимаю, тот факт что this.counter берется из прототипа на производительность не повлиял — по крайней мере, в этом тесте.
Как я понимаю, тот факт что this.counter берется из прототипа на производительность не повлиял — по крайней мере, в этом тесте.
Он влияет только если создать больше одного объекта и у обоих дернуть метод пару раз — иначе незаметно (связанно это с тем, что inline caches проходят через PREMONOMOPRHIC state и это скрывает полиморфизм, второй объект выявляет это — потому что PREMONOMOPRHIC уже пройден и мы находимся в MONOMORPHIC). Посмотрите на
Lava method call (2)
в моей версии.В том и дело, что ваш Lava method call (2) исполняется ровно с такой же скоростью как и остальные.
А в Firefox он вообще в 2 раза быстрее всех остальных.
А в Firefox он вообще в 2 раза быстрее всех остальных.
Если взять открыть в чистой вкладке в Хроме и выполнить сначала «Lava method call», а потом «Lava method call (2)», то должна нарисоваться такая картина. Если их выполнить в каком-то другом порядке или допусти выполнить «Lava method call» еще раз после того как «Lava method call (2)» открутится, то «Lava method call» будет такой же медленный как и «Lava method call (2)», потому что type feedback стал полиморфный.
С FF картина интересная, все что я говорю — оно только к V8 относится. Скорее всего в этом микробенчмарке, он (совершенно правильно) понимает что полиморфизма тут быть не может. Ох, если бы только было легко посмотреть в их сгенерированный код, без пересборки jsshell из исходников.
С FF картина интересная, все что я говорю — оно только к V8 относится. Скорее всего в этом микробенчмарке, он (совершенно правильно) понимает что полиморфизма тут быть не может. Ох, если бы только было легко посмотреть в их сгенерированный код, без пересборки jsshell из исходников.
Теперь вижу, еще раз благодарю за консультацию, буду улучшать производительность.
Только у меня падение скорости не в 2 раза, а так:
Lava method call in child class (client generated class) = 13,498
Lava method call in child class (server generated class) = 11,233
Lava method call (2) = 9,283
Только у меня падение скорости не в 2 раза, а так:
Lava method call in child class (client generated class) = 13,498
Lava method call in child class (server generated class) = 11,233
Lava method call (2) = 9,283
Конкретное значение уже зависит от CPU и т.д.
Вот после некоторых мучений нарисовал тест демонстрирующий проблему с полиморфизмом на FF
jsperf.com/liquidlava-class-system-performance-ff
FF не микробенчмарке действительно очень умело избегает этой полиморфной ловушки, но это срабатывает только если бенчмарк постоянно крутится с одним и тем же объектом.
Вот после некоторых мучений нарисовал тест демонстрирующий проблему с полиморфизмом на FF
jsperf.com/liquidlava-class-system-performance-ff
FF не микробенчмарке действительно очень умело избегает этой полиморфной ловушки, но это срабатывает только если бенчмарк постоянно крутится с одним и тем же объектом.
Разрешите уточнить:
Правильно будет именно копировать функции, или же достаточно все начальные значения присваивать в конструкторе, а из прототипа убрать? (все-равно конструктор генерируется)
Правильно будет именно копировать функции, или же достаточно все начальные значения присваивать в конструкторе, а из прототипа убрать? (все-равно конструктор генерируется)
Это решает проблему с «отложенной» инициализацией полей и, как следствие, убирает проблему нестабильной формы объекта, копирование функций решает другую проблему — если у вас есть разные классы наследники, то функция сидящая на прототипе базового класса будет видеть их всех — и как следствие будет полиморфной, даже если каждый отдельный класс наследник будет производить объекты стабильной формы. Если каждый класс наследник пользуется своей копией «базовой» функции — то каждая такая копия мономорфна.
Посмотрел сообщения от RubaXa, вспомнил специфику. Там сравнивалось
Base.prototype.method.call(this)
и __.parent.call(this)
, где __
указывал на method
в потомке, а __.parent
на метод в базовом классе, т.е. Base.prototype.method
. Причем все это было год назад, до того как V8 научился распознавать f.call
. Из-за этого .call
не проинлайнивался, и как следствие LICM
не выносил LoadFunctionPrototype
из цикла. Как следствие каждая лишняя инструкция была на счету и отражалась на результате микробенчмарка. (Плюс еще во втором случае на одно разыменование меньше, все складывается)В этом примере не будет работать оператор typeof, но на практике без него можно прекрасно обходиться. Я говорю про реальные приложения и задачи: если вам нужно отличать тип Animal от Cat — то это реальная задача, и она прекрасно решается. Но если вы хотите делать это оператором typeof — то извините, вам к другому доктору.
Эммм… Вы видимо опечатались. Возможно имелось ввиду instanceof, а typeof как бы возвращает у всех объектов «object».
1. Какие изменения в архитектуре приложения у вас произошли, и с какими трудностями столкнулись при переходе к ООП-коду? (особенности верстки и т.п.)
2. Можно ли при создании класса задать набор свойств? Есть ли их значение по умолчанию? Как вы присваиваете большое количество свойств?
Пример:
2. Можно ли при создании класса задать набор свойств? Есть ли их значение по умолчанию? Как вы присваиваете большое количество свойств?
Пример:
function Dog(name) {
this.name = name || "Тузик"; // Когда таких свойств у объекта много, вы так же пишите или по другому?
}
1) В JavaScript я начинал именно с MooTools и ООП. У меня были большие проекты, где нужна надежность и качество, так что ООП — это красота и спасение, а не зло. Трудности возникают если есть лапша неструктурированного кода, которую приходится переписывать.
2) Конечно, можно. Разрешите пригласить вас в исходник основного фреймворка
— там больше 100 классов, которые решают задачи из реальной жизни.
Пример свойств по умолчанию:
2) Конечно, можно. Разрешите пригласить вас в исходник основного фреймворка
— там больше 100 классов, которые решают задачи из реальной жизни.
Пример свойств по умолчанию:
Lava.define('Lava.Something', {
// все это будет скопировано для каждого экземпляра класса
name: "Вася",
names: ["Коля", "Петя"],
number: 123
});
То, что абсолютно нормально в С++, бывает очень вредным в JavaScript. Я говорю это из своего опыта: если в классах есть приватные методы и переменные, то такие классы часто становятся неподдерживаемыми. Если вы хотите что-то изменить в таком куске кода — то иногда вам не остается ничего кроме как выбросить старый код и переписать всё с нуля.
В C++ есть бесценный модификатор доступа
protected
. Без него код с классами и наследованием скатывается в одну из двух крайностей — либо все кишки торчат наружу, либо класс будет сложно расширить. Даже в ES6 такое не планируется, однако есть статья, где предлагается неплохой способ это реализовать. Могу перевести ее для хабра, если нужно.Приватные члены — это плохая практика.
А как же инкапсуляция?
Инкапсуляция не нарушается. Приватный член класса — он так же является и защищенным, но с более сильными ограничениями.
Когда пишешь большое приложение на том же C++ — то почти все члены класса у вас будут именно защищенными, а не приватными, согласны?
Другие дело, что в JavaScript за настоящую приватность вы платите слишком большую цену, разговор был именно об этом, так что другими словами обмен получается невыгодным.
Когда пишешь большое приложение на том же C++ — то почти все члены класса у вас будут именно защищенными, а не приватными, согласны?
Другие дело, что в JavaScript за настоящую приватность вы платите слишком большую цену, разговор был именно об этом, так что другими словами обмен получается невыгодным.
Когда пишешь большое приложение на том же C++ — то почти все члены класса у вас будут именно защищенными, а не приватными, согласны?
Нет, не согласен. Я, правда, пишу не на C++, а на C#, но у меня нет смещения баланса в сторону protected-членов (кроме абстрактных классов).
Вообще не согласен. Члены классы должны быть приватными, а всё что нужно наружу должно реализовываться через интерфейсы. Protected оправдан только для очень тесно взаимодейтсвующих классов, как правило в пределах одного юнита.
Приватные члены — это, строго говоря, т.н. «сокрытие данных», а не инкапсуляция.
Плохой практикой ее можно назвать по двум причинам:
1) Если переборщить с приватными членами, мы бессмысленно затрудним наследование пользователю.
2) Приватные члены в JS в лучшем случае эмулируются, и без препроцессоров эта эмуляция не очень удобна. Так зачем мучаться? ЕМНИП, в Python тоже их нет, но, вроде бы, никто такими вопросами не задается.
Плохой практикой ее можно назвать по двум причинам:
1) Если переборщить с приватными членами, мы бессмысленно затрудним наследование пользователю.
2) Приватные члены в JS в лучшем случае эмулируются, и без препроцессоров эта эмуляция не очень удобна. Так зачем мучаться? ЕМНИП, в Python тоже их нет, но, вроде бы, никто такими вопросами не задается.
Приватные члены — это, строго говоря, т.н. «сокрытие данных», а не инкапсуляция.
Инкапсуляция, в том числе, включает в себя сокрытие данных.
Плохой практикой ее можно назвать по двум причинам:
1) Если переборщить с приватными членами, мы бессмысленно затрудним наследование пользователю.
Вы говорите только про JS, или про любой ОО-язык?
Нет, инкапсуляция — это объединение данных и поведение в одной сущности. Сокрытие данных, как видите, штука ортогональная.
> Вы говорите только про JS, или про любой ОО-язык?
что именно?
> Вы говорите только про JS, или про любой ОО-язык?
что именно?
Нет, инкапсуляция — это объединение данных и поведение в одной сущности. Сокрытие данных, как видите, штука ортогональная.
О, wiki-war.
In programming languages, encapsulation is used to refer to one of two related but distinct notions, and sometimes to the combination thereof:
A language mechanism for restricting access to some of the object's components.
A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data.
Some programming language researchers and academics use the first meaning alone or in combination with the second as a distinguishing feature of object-oriented programming, while other programming languages which provide lexical closures view encapsulation as a feature of the language orthogonal to object orientation.
Я отношусь к этим «some programming language researchers and academics», уж простите.
Вы говорите только про JS, или про любой ОО-язык?
что именно?
Вот это:
Если переборщить с приватными членами, мы бессмысленно затрудним наследование пользователю.
Ну так инкапсулировать можно и без приватных членов.
В ООП? Интересно, каким образом.
Разве объединение методов и свойств в объект (класс) не является их инкапсуляцией?
Зависит от терминологической базы. Я отношусь к той части сообщества, которая считает, что сокрытие информации является неотъемлимой частью инкапсуляции.
Согласен, но в чем цель сокрытия информации?
В сокрытии сложности.
Что если скрыть сложность в псевдо-приватных методах/свойствах (они будут публичные, но о них не будет знать пользователь)?
Пользователь всегда знает о публичных методах и свойствах. Смысл сокрытия информации именно в том, чтобы пользователь не тратил силы на разбирательство, что он должен знать, а что — нет.
Для ЯП, в которых отсутствует механизм сокрытия данных, используется префикс "_" для сообщения пользователю о том, что на эти данные не нужно тратить силы для их разбирательства.
Префикс — это соглашение. Соглашение требует обработки. Обработка требует сил.
Именно поэтому для сокрытия данных лучше использовать механизмы языка, а не просто соглашения.
Именно поэтому для сокрытия данных лучше использовать механизмы языка, а не просто соглашения.
Все требует обработки. Как минимум в классе необходимо найти область public прежде чем изучать его интерфейс.
… однако при попытке обратиться к любому не-публичному свойству ошибка будет от компилятора, а не от собственного мозга. Вот и бонус.
Значит дело все таки не в сокрытии сложности, а в защите членов класса и переносе ответственности на компилятор (как в случае со строгой типизацией)?
Во-первых, это сокрытие сложности, которое усилено компилятором.
А во-вторых, в некоторых языках/платформах private-члены вам вообще никак не видны (ну кроме прямого влезания в код).
А во-вторых, в некоторых языках/платформах private-члены вам вообще никак не видны (ну кроме прямого влезания в код).
Во-первых, это сокрытие сложности, которое усилено компилятором.
Вот автор и считает, что это усиление компилятором излишне.
А во-вторых, в некоторых языках/платформах private-члены вам вообще никак не видны (ну кроме прямого влезания в код).
Без прямого влезания в код (чтение документации) можно скрыть и без private. Все зависит от используемого генератора документации.
Вот автор и считает, что это усиление компилятором излишне.
Ну и зря. Количество информации неизбежно увеличивается, ее сокрытие уменьшается. Другое дело, что в конкретно взятом js может быть невозможно реализовать истинное сокрытие информации — так это не означает, что оно не нужно, это означает, что его нельзя сделать, и надо смириться.
Без прямого влезания в код (чтение документации) можно скрыть и без private. Все зависит от используемого генератора документации.
Документацией дело не ограничивается. Еще есть API browsers и так далее.
в конкретно взятом js может быть невозможно реализовать истинное сокрытие информации
Возможно.
Еще есть API browsers
Если не лезть в исходники, то всегда все зависит от используемой системы.
Если не лезть в исходники, то всегда все зависит от используемой системы.
Еще зависит от того, что сразу заложено в платформу. Например, в .net изначально есть понятие публичного интерфейса сборки.
Ну, если язык позволяет создавать Интерфейсы, можно публичку описать в них и имплементировать в класс.
Совершенно верно. Так же можно поступить в любом ЯП без private.
Ну как бы Интерфейсы есть не в любом языке. Опять же предложенный способ не каноничен, но если уж сложно найти методы с модификатором public, то можно и в отдельном Интерфейсе описать их.
Ну как бы Интерфейсы есть не в любом языке
Что мешает реализовать, в случае отсутствия?
Отсутствие множественного наследования, например?
Стало любопытно.
А есть языки, в которых нет интерфейсов, и нет множественного наследования?
А есть языки, в которых нет интерфейсов, и нет множественного наследования?
Множественное наследование это другое. Для реализации интерфейсов достаточно хранить список реализуемых классом интерфейсов в виде системного свойства класса и проверить, реализованы ли все приведенные в интерфейсе методы в классе при его объявлении, иначе выдать ошибку.
Кому хранить, кому проверять?
Ну это уже вопрос реализации. Можно хранить прямо в классе, это позволит реализовать метод instanceof с учетом интерфейса, можно в неком менеджере, дабы не заполнять класс системными свойствами.
Вы говорите про проверки времени выполнения, реализованные прямо в коде силами программиста?
По другому никак. Не совсем программистом, можно использовать менеджер для этих целей.
… который написан программистом. Соответственно, каждый следующий программист, подключающийся к проекту, должен знать, что надо использовать менеджер, а не обычные объекты. И это уменьшение сложности?
каждый следующий программист, подключающийся к проекту, должен знать, что надо использовать менеджер
Как и каждый программист, должен знать синтаксис ЯП.
это уменьшение сложности?
Так дело ведь не в сложности. Если что то есть из коробки, это намного проще, чем сделанное руками, но речь идет о таких ЯП, в которых нет из коробки.
Как и каждый программист, должен знать синтаксис ЯП.
Меньше необходимых знаний — меньше ошибок, как это ни удивительно.
но речь идет о таких ЯП, в которых нет из коробки.
… а то, что вы получаете — не то же самое, что из коробки, а подобие.
Меньше необходимых знаний — меньше ошибок, как это ни удивительно.
Ничуть. Такой же объем знаний, такой же объем ошибок (если все написано качественно).
а то, что вы получаете — не то же самое, что из коробки, а подобие
Совсем не согласен. То что есть из коробки, тоже подобие чего то другого и так далее. Размытый аргумент, чтобы им оперировать.
На деле разница будет только в двух аспектах:
- На каком этапе обнаруживается ошибка
- Защита от дурака, который зачем то решит полезть в закрытую область класса
Ничуть. Такой же объем знаний, такой же объем ошибок (если все написано качественно).
Не такой же. Нужно знать ЯП + ваш фреймворк. Вместо того, чтобы знать только ЯП.
На каком этапе обнаруживается ошибка
Этого аргумента достаточно.
Как бы много чего может помешать))) Максимум что у вас получится, если делать руками — это костыльная подделка, но зачем их вообще реализовывать руками, я не знаю. Мой кейс про интерфейсы был также эфемерен, как и ваше сетование на сложность изучения публичного интерфейса класса. На мой взгляд, ниче сложного в этом нет, особенно если присутствует группировка методов по модификаторам (public наверху) в коде. Интерфейсы все же для другого создавались)
костыльная подделка, но зачем их вообще реализовывать руками
Я тоже не знаю зачем их реализовывать, когда можно прекрасно жить без интерфейсов.
ваше сетование на сложность изучения публичного интерфейса класса
Вы меня с кем то путаете. Я подобного не говорил.
если присутствует группировка методов по модификаторам
У меня группировка по сверткам (folding) используется, это еще более простой вариант.
Private поля (и методы) решают вовсе не задачу сокрытия сложности. Не надо считать пользователей ваших классов идмотами, которых пугает код. Тем более, что информация как правило и не скрывается, можно посмотреть h файл.
Поля закрывают для гарантии того, что объекты класса будут находиться в инварианте на границах вызова своих методов.
И если класс предполагает уточнение своей реализации, т.е. не объявлен как final, то логично полагать, что потомки изменят и инварианты объектов. А, значит, private будет только мешать.
Конечно, если класс описывает сложный объект, часть состояния которого управляется только им и не должна быть доступна потомкам (подсчет ссылок, к примеру), private данные в нём уместны. Но вместе с тем в таких ситуациях может бфть правильным решением будет перенос части функционала в другой класс, который будет включён, или от которого будет наследование.
Так что получается, что private наиболее целесообразна для объявления методов, которые нигде не определяются :)
Поля закрывают для гарантии того, что объекты класса будут находиться в инварианте на границах вызова своих методов.
И если класс предполагает уточнение своей реализации, т.е. не объявлен как final, то логично полагать, что потомки изменят и инварианты объектов. А, значит, private будет только мешать.
Конечно, если класс описывает сложный объект, часть состояния которого управляется только им и не должна быть доступна потомкам (подсчет ссылок, к примеру), private данные в нём уместны. Но вместе с тем в таких ситуациях может бфть правильным решением будет перенос части функционала в другой класс, который будет включён, или от которого будет наследование.
Так что получается, что private наиболее целесообразна для объявления методов, которые нигде не определяются :)
И если класс предполагает уточнение своей реализации, т.е. не объявлен как final, то логично полагать, что потомки изменят и инварианты объектов.
А как же LSP?
Не понял вопроса. Как вы связываете изменение инвариантов потомками и lsp?
Изменение инвариантов приводит к нарушению LSP (потому что объект унаследованного класса ведет себя не так, как объект базового).
Почему изменение инвариантов должно привести к изменению поведения? Инвариант это состояние объекта. При изменении инвариантов контракты менять вовсе не обязательно :)
Мы вот про этот инвариант говорим?
Потому как в моем понимании инвариант — это набор условий, который всегда верен для объекта. Если он меняется между объектами — то (при некоторых изменениях, не всех) один объект нельзя использовать вместо другого, потому что наши ожидания о его инварианте не будут совпадать с реализацией.
Потому как в моем понимании инвариант — это набор условий, который всегда верен для объекта. Если он меняется между объектами — то (при некоторых изменениях, не всех) один объект нельзя использовать вместо другого, потому что наши ожидания о его инварианте не будут совпадать с реализацией.
Lsp говорит о том, что наследник не должен усиливать предусловия и ослаблять постусловия.
Ок, я понял, что ожидая худшего вы исходите из того, что наследник сделает именно это: усилит предусловие и начнёт кидать исключения там, где базовый класс работает корректно.
Имхо пред и постусловия относятся к тем ограничениям, которые обязательно должны быть покрыты тестами. И этой защиты достаточно. Можно и нужно исходить из того, что программист, наследующий от класса, должен понимать что и как реализовано в нём.
А объявление закрытыми полей, значение которых определяется алгоритмами, переопределяемыми наследниками, зачастую ведёт к ненужному увеличению сложности.
Как вы предлагаете устанавливать состояние объекта в производных классах?
Ок, я понял, что ожидая худшего вы исходите из того, что наследник сделает именно это: усилит предусловие и начнёт кидать исключения там, где базовый класс работает корректно.
Имхо пред и постусловия относятся к тем ограничениям, которые обязательно должны быть покрыты тестами. И этой защиты достаточно. Можно и нужно исходить из того, что программист, наследующий от класса, должен понимать что и как реализовано в нём.
А объявление закрытыми полей, значение которых определяется алгоритмами, переопределяемыми наследниками, зачастую ведёт к ненужному увеличению сложности.
Как вы предлагаете устанавливать состояние объекта в производных классах?
Имхо пред и постусловия относятся к тем ограничениям, которые обязательно должны быть покрыты тестами. И этой защиты достаточно.
… и тест, который будет автоматически проверять на них все классы, унаследованные от x?
Можно и нужно исходить из того, что программист, наследующий от класса, должен понимать что и как реализовано в нём.
В этом случае мы снова теряем сокрытие сложности, и это плохо.
Как вы предлагаете устанавливать состояние объекта в производных классах?
У меня нет единого ответа. В каждом случае по-своему.
Зачем имена в строках и какие-то пространства имён?
Почему не просто:
var MyClass = Lava.ClassManager.define({
Extends: ParentClass
});
Почему не просто:
var MyClass = Lava.ClassManager.define({
Extends: ParentClass
});
Есть весомая причина: в вашем примере ParentClass уже должен существовать на момент вызова define.
А с моим подходом я складываю тела классов в массив Lava.classes, и сами классы создаю уже в Lava.init().
Отложенное создание позволяет сделать monkey-патчинг для классов ядра (кто знает, что может понадобиться программистам, которые будут мой продукт использовать).
В вашем подходе этого делать нельзя, в лучшем случае заменяется целый файл в сборке, а в моем — можно заменить даже метод в классе, который находится в начале цепочки наследования, причем не изменяя файлы проекта.
А с моим подходом я складываю тела классов в массив Lava.classes, и сами классы создаю уже в Lava.init().
Отложенное создание позволяет сделать monkey-патчинг для классов ядра (кто знает, что может понадобиться программистам, которые будут мой продукт использовать).
В вашем подходе этого делать нельзя, в лучшем случае заменяется целый файл в сборке, а в моем — можно заменить даже метод в классе, который находится в начале цепочки наследования, причем не изменяя файлы проекта.
Интересный проект. Система классов сильно напомнила классы из Sencha/ExtJS.
К чему то похожему пришел в VimScript, но в связи с особенностями (читать — ограничениями) языка, пришлось пойти по пути Perl.
Интересное решение, обязательно попробую его в каком нибудь проекте.
Интересное решение, обязательно попробую его в каком нибудь проекте.
Почему не использовать TypeScript?
Это такой же вопрос как «Ява против C#».
ClassManager и TypeScript — это два разных инструмента, которые решают одну и ту же задачу разными способами.
TypeScript крут! Поддержка IDE, рефакторинг… но вот те, кто сидят на линуксе — не пользуются IDE от Microsoft. Лично мне без разницы, у меня стоит Visual Studio. Давайте поищем более существенные отличия.
В комментарии выше я уже упоминал, что классы из моего фреймворка можно monkey-патчить, а в случае TypeScript, если не ошибаюсь, то вам нужно заменить весь файл с классом. То есть в моем случае у вас больше контроля.
ClassManager позволяет патчить классы во время выполнения, а TypeScript нет.
Ну и, наконец, разница в скорости.
Какой инструмент выбрать — чаще всего зависит от решаемой задачи.
ClassManager и TypeScript — это два разных инструмента, которые решают одну и ту же задачу разными способами.
TypeScript крут! Поддержка IDE, рефакторинг… но вот те, кто сидят на линуксе — не пользуются IDE от Microsoft. Лично мне без разницы, у меня стоит Visual Studio. Давайте поищем более существенные отличия.
В комментарии выше я уже упоминал, что классы из моего фреймворка можно monkey-патчить, а в случае TypeScript, если не ошибаюсь, то вам нужно заменить весь файл с классом. То есть в моем случае у вас больше контроля.
ClassManager позволяет патчить классы во время выполнения, а TypeScript нет.
Ну и, наконец, разница в скорости.
Какой инструмент выбрать — чаще всего зависит от решаемой задачи.
Очень странный тест, либо я ничего не понял. Зачем тестировать Backbone в такого рода тесте? В реализации наследования никакой разницы с нативным наследованием(через прототипы) не будет, а для ситуаций когда нужен «общий массив» то существуют другие структуры.
Для полной картины все таки желательно собрать побольше фреймворков. В статье было упомянуто, что если прототип присвоить как объект, то в Firefox падает скорость создания классов. Возможно, какие-то фреймворки используют Object.create — это обязательно будет влиять на скорость, или может знают некоторые хитрости, чтоб ускорить свои классы в определенных браузерах…
Всё что делает extend это
т.е по факту просто копирует каждое из свойств(в том числе и prototype-chain) в новый объект, наследование там нужно только тогда когда некоторые функции родителя будут изменены со временем(например благодаря прототипам я не парюсь на счёт зависимостей и порядка выполнения и загрузки кода).
Я к тому что в этом тесте никакого смысла тестировать библиотеку нет, она ничего нового в классическую реализацию наследования через прототипы не вносит, да ещё и работает в разы дольше просто потому что взаимодействие между частями организовано по-другому(нет нужды в приватных переменных, а если ОЧЕНЬ надо — то они обычно организованы через замыкание), да ещё и всякая фигня при инициализации происходит(дефолты, сет, определение айдишника...). Так что слегка некоретно сравнивать один механизм наследования с цепочкой инициализации экземпляра класса.
extend, опуская шаманства с конструктором и цепочкой прототипа
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
if (source) {
for (var prop in source) {
obj[prop] = source[prop];
}
}
});
return obj;
};
т.е по факту просто копирует каждое из свойств(в том числе и prototype-chain) в новый объект, наследование там нужно только тогда когда некоторые функции родителя будут изменены со временем(например благодаря прототипам я не парюсь на счёт зависимостей и порядка выполнения и загрузки кода).
Я к тому что в этом тесте никакого смысла тестировать библиотеку нет, она ничего нового в классическую реализацию наследования через прототипы не вносит, да ещё и работает в разы дольше просто потому что взаимодействие между частями организовано по-другому(нет нужды в приватных переменных, а если ОЧЕНЬ надо — то они обычно организованы через замыкание), да ещё и всякая фигня при инициализации происходит(дефолты, сет, определение айдишника...). Так что слегка некоретно сравнивать один механизм наследования с цепочкой инициализации экземпляра класса.
В угоду «экономии на спичках» жертвовать нормальным синтаксисом, поддержкой IDE, все возможных линтеров, а в дальнейшем нативной реализацией, и получить уродский синтаксис, непонятную структуру но зато выигрыш в мифических попугаях.
А по опыту я могу сказать, что тормозить в вашем приложении будут не создание классов, а манипуляции с DOM и тупые алгоритмы работы с данными.
А по опыту я могу сказать, что тормозить в вашем приложении будут не создание классов, а манипуляции с DOM и тупые алгоритмы работы с данными.
Похоже на то, что вы пытаетесь сделать JS более похожим на Python ( underscore-методы и переименования в цепочке наследования). Так что мне уже нравится. :) Ещё бы научить JS строгой типизации.
А в чем проблема? Используйте flowtype.org/
А вы сравнивали ваш генератор с тем же coffee.script?
Есть более красивый вариант, нежели
(можно rest заменить на arguments, конечно)
Вызов
Cat.prototype.Animal$init = Animal.prototype.init
, и, кажется, не сильно дороже. Вот такой вот:Cat.prototype.super = function(name, ...rest){
return Animal.prototype[name].apply(this, rest);
};
(можно rest заменить на arguments, конечно)
Вызов
this.super('init');
всё же смотрится лучше, нежели this.Animal$init();
.Sign up to leave a comment.
Очень быстрые классы на JavaScript с красивым синтаксисом