Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
Форум программистов > JavaScript: Общие вопросы > Наследование и состояние над-класса |
Автор: Се ля ви 5.6.2008, 15:32 | ||
Продолжаем эпопею, начатую в теме "http://forum.vingrad.ru/forum/topic-214727.html". Теперь вспомним про состояния объектов. Использование http://karaboz.ru/2007/10/04/oop-v-java-script-svojstva-i-metody-klassa/ открывает для нас возможность закрывать внутреннее состояние объекта от использования его извне. Состоянием называется совокупность значений полей объекта. А теперь вспомним, что при реализации наследования, основанного на прототипах, мы присваиваем свойству "prototype" конструктора объект, сгенерированный по конструктору предка - т.е. объект предка. Соответственно, у всех объектов-наследников будет ссылка на один и тот же объект предка и если мы будем менять его состояние, то оно будет меняться для всех объектов-наследников сразу. Такое поведение недопустимо при серёзном использовании наследования для расширения одним конструктором другого:
Как вы думаете, что с этим можно сделать? |
Автор: Се ля ви 5.6.2008, 17:16 | ||||||
Как вариант напрашивается объявить класс-наследник примерно так:
Чуть-чуть модифицируем код с учётом того, что ссылку на конструктор предка мы можем получить и из внутренних источников:
Т.е. фактически, мы обновляем свойство "prototype" каждый раз при создании объекта, что бы каждому новому экземпляру доставался новый экземпляр-объект, полученный из родительского конструктора. Этот метод будет работать но, к сожалению, будет иметь один существенный изъян - это вариант лишь для конструкторов, не использующих параметры инициализации (т.н. формальные параметры). Если же конструктору предка нужно будет передать параметр - он при такой схеме попадёт только к следующему объекту-наследнику, но не к данному:
Так что такое решение в общем случае неприемлемо... |
Автор: Се ля ви 5.6.2008, 23:50 | ||||||||
Нужно по прежнему реализовать наследование так, что бы получить возможность писать крупные приложения на JavaScript. В данном случае нужно сделать так, что бы каждый экземпляр, созданный конструктором "B", имел неявную ссылку на свой объект, созданный конструктором "A" и что бы это по возможности не сказалось на пользователе - т.е. на той части программы, в которой объявляются и используются объекты-экземпляры, созданные на основе конструкторов.
Посредством изменения значений его полей. Для public полей - напрямую, для private - опосредованно setter`ами.
Это простейший пример инкапсуляции, приём http://en.wikipedia.org/wiki/Information_hiding (если чего-то непонятно, с переводом могу помочь ![]() Объект содержит private-поле _privateField, которое видно только внутри объекта и не видно снаружи, соответственно я, как составитель библиотеки, получаю некоторую гарантию, что программист, который будет эту мою библиотеку использовать, не сможет получить прямого доступа к этому полю. Например, я мог бы наложить некоторые дополнительные ограничения - заставить передавать числовое значение в каком-то ограниченном диапазоне и/или кратное какому-то числу и сделать проверку на соответствие этим ограничениям перед присвоением значения полю. Ну или как в случае с методом "setPrivateField" сделать проверку на соответствие нужному типу (number) и ввести преобразование, если значение number было передано в оболочке - объекте Number:
Таким образом, после прохождения такой проверки, я полностью уверен, что какой бы дуралей ни использовал этот класс, поле _privateField всегда будет типа "number", так что я могу без опаски производить с ним в других методах объекта арифметические операции. Т.е. объект получается надёжным. Такого рода ограничения не нужны, когда над проектом работает один человек или даже несколько, которые сидят рядом и всегда могут друг друга обо всём спросить или легко разберутся в коде друг друга, но для больших проектов и написания библиотек для сторонних пользователей это утверждение не справедливо, по-этому приходится писать надёжный код. |
Автор: Се ля ви 6.6.2008, 02:14 | ||
Итак, похоже единственный приемлемый ответ на вопрос о том, как добиться желанной цели состоит в том, что бы архитектурно не воспринимать конструктор как метод, устанавливающий какое-то состояние для нашего объекта в зависимости от параметров его инициализации, а создавать для установки состояния специальный метод. Предлагаю, назвать этот метод "init" - на мой взгляд, это наиболее приемлемое название. Тогда на реализацию этого метода необходимо наложить следующие ограничения: 1. Для этого метода нужно обеспечить, что бы его вызывали только один раз или не вызывали вовсе если его нет (если нужное состояние объекта выставляется автоматически или отсутствует вовсе). Для этого проще всего уничтожать ссылку на этот метод после его выполнения - тогда проинициализирован объект или нет можно будет понять просто проверив, есть ли у него такой метод. 2. В этом методе нужно вызывать такой же метод "init" предка конструктора, если они существуют, передавая им нужные параметры инициализации. При чём желательно делать это в самом начале их выполнения, что бы иметь гарантию того, что когда данный объект начнёт выполнять какие-то методы, объект предка уже будет в правильном проинициализированном состоянии. 3. Если у класса-предка есть метод "init", то у класса-потомка он непременно должен быть Т.к. этот метод будет перекрываться, нам нужна чёткая ссылка на объект-прототип для его вызова (кстати, то же будет касаться любых других методов - перекрыв метод, мы сможем выполнить дополнительные действия, а потом вызвать переопределяемый метод). По аналогии с Java назвать эту ссылку "super" не получится, поскольку это слово закрыто для использования составителями языка, по-этому, учитывая приватность этого поля, предлагаю, как и для других private-полей, добавить к началу названия символ "_". Вообще, почему я приставил к имени закрытого поля символ "_"? Дело в том, что в JS нельзя разделить private-поле и параметр, переданный в функцию, так что поля обязаны называться по-другому, что бы быть доступными в методах с такими же именами параметров - например, в setter`е мы не можем написать, в отличие от Java, "this.a = a" потому, что "this.a" никуда не ссылается, а private-поле "a" оказывается перекрыто одноимённым параметром - отсюда и предложение писать символ "_" перед именами переменных, соответствующих закрытым полям. Что бы не нарушать синтаксис создания экземпляра объекта, полезно вызывать метод "init" в том случае, если параметры инициализации ему всё-таки были переданы в конструктор - это означает, что объект используется непосредственно, а не как прототип. Предыдущий приём отметать по-этому рано. Нужно просто его дополнить некоторыми деталями: 1. Присваивать свойству "prototype" конструктора объект A без параметров инициализации. 2. Присваивать значение private-полю "_super" нужно между присвоением будущему объекту прямой ссылки на его конструктор и присвоением свойству "prototype" нового объекта предка, потому что после этого присвоения мы до нужного нам объекта уже не доберёмся (доберёмся по ссылке __proto__, но только в FF, а в общем виде у нас будет только неявная ссылка). 3. Всё, что было описано выше про метод "init".
Вот теперь нам действительно не важно - передали нам ссылку на объект A или на объект B - если нас интересует только функциональность объекта A, то мы работаем с объектом как с объектом, созданным конструктором A, хотя реально это может быть и объект, созданный конструктором B. И всё это может работать на сколько угодно шагов в дереве иерархии наследования. |
Автор: AKS 6.6.2008, 09:34 | ||||||||||||||
А-а-а, вот оно что! Тогда я тут не смогу помочь ничем - "крупные приложения на JavaScript" не писал и вряд ли придется. ;) А что это за объект предка? Прототип что-ли? Спасибо, удружили! Теперь наконец-то буду знать, что такое инкапсуляция! ![]() Если серьезно, то вопрос задавал для того, чтобы узнать, зачем она нужна там, где все от природы public (дальнейшие ваши объяснения все прояснили).
А Вас не волнует та цена, которую придется платить за такие возможности в J(ava)Script? Если Вы делаете примерно то же самое в Java-программе, то ваши методы ведь будут общими для всех экземпляров, так ведь? А что происходит в данном случае? Всякий раз, наряду с приватной переменной, Вам необходимо создать пару методов (геттер и сеттер), которые будут уникальны для каждого экземпляра. Т.е. с каждым объектом-экземпляром создаются еще объекты-функции, размещаемые в динамической памяти. Нужны ли такие программные единицы, отрицательно влияющие на потребление памяти и снижающие производительность, да еще и в "приложениях", где критически важны время отклика и интерактивность?
Типичное заблуждение. Я сам ничего придумывать не буду - возьму готовое. Вот http://www.rsdn.ru/Forum/message/2462479.all.aspx на тему "jQuery – это сила". Там Zeroglif показал народу, слабо представляющему что такое J(ava)Script, насколько примитивны подобные ухищрения. Было (функция из либы):
Вдохновенные комменты к этой функции были, вроде: "У нее ровно одна задача — узнать, является ли передаваемый параметр функцией. Со своей задачей она справляется". Справляется, как бы не так! Zeroglif написал:
Люди даже въехать-то не смогли, что и как без дополнительных объяснений. Вот так. Что я хотел этим сказать? А то, что если Вы собираетесь писать для "дуралеев" (ваше выражение), то это бесполезная трата времени. Просто-напросто Вам не позволит этого сделать сам язык программирования, за который Вы взялись. И не стоит забывать о золотом правиле: не пытайтесь предугадать все варианты использования вашей программы
Надежный код на J(ava)Script – это утопия. Всем будет удобней, если код будет "прозрачным" и ясным. Именно этого и нужно добиваться, вместо того, чтобы писать "защиту от дурака".
Ну а это-то на кой черт? Что и говорить - Java-программист не может обойтись без своего класса обработки исключений. Но а кому Вы собираетесь показывать свои alert’ы? Пользователям эти "дурно пахнущие" сообщения даром не нужны, а программист-неудачник пусть уж увидит встроенные сообщения об ошибке. Да и что может быть лучше "родной" обработки исключений? К тому же, есть ведь, в конце концов, try catch плюс conlsole.log/opera.postError/Debug.write. В заключении - следующее. Я вижу, что у Вас уже сформировалась определенная концепция, и Вас уже с этого пути не свернуть. Явление уже привычное – кто родом с Java, кто с C(++) и т.д., выбирают привычные методы достижения своих целей и не желают (или не могут) действовать иначе. Если Вы уверены, что такой простецкий язычишка, как J(ava)Script, даст Вам то, что нужно, то я желаю только удачи в разработке ваших "хитрых супер-пупер приложений" (больше не буду влезать в концептуальные споры). |
Автор: Се ля ви 6.6.2008, 11:41 | ||||||||||||||
Ну хорошо, приложение уровня GMail, на ваш взгляд, можно написать как "некрупное приложение"? Если бы вам пришлось - вы бы как действовали?
Точно! Об этом я как-то подзабыл - очень хорошо, что вы мне это заметили. Действительно - в Java все методы содержатся в классах, в ед. экземпляре, а тут получается, что я расточительно создаю каждый раз новый объект-функцию. Спасибо, что обратили на это моё внимание! ![]()
Помнится, слово "прототип" как раз Zeroglif и http://forum.vingrad.ru/index.php?showtopic=208931&view=findpost&p=1496917 - что из-за повсеместного использования в разных контекстах оно всех только путает. Да, в данном случае имелся в виду прототип. ![]()
AKS, поверьте, эти правила не мной придуманы. Когда проект пишут 20 программистов, про которых нельзя сказать, что они - "не разлей вода", ибо у всех разные подходы и видение проекта, в результате чего периодически возникают мелкие конфликты и плетутся интриги, очень часто возникает ситуация, когда один разработчик обвиняется в том, что его код неправильный - ему угрожает лишение премии, понижение зарплаты или даже увольнение. А настоящая причина оказывается в том, что его код какой-то другой программист неправильно использует. И начинаются бодания типа: "Откуда я знал, что твоей функции можно передавать только 'number'? Я думал, она и строку примет нормально и распарсит..." или "Сам и проверяй свой диапазон, почему я-то это должен делать?.." И это ещё будет очень хорошо, если вам вообще это предъявят, а в худшем случае сделают о вас выводы как о разработчике без предъявления претензий и это и другие такие вещи постепенно сформируют соответствующее отношение к Вам у начальства и коллег. А вы даже не будете понимать, в чём дело до самого последнего момента, когда станет уже поздно разбираться и вспоминать, в чём причина такого отношения. В итоге в таких проектах всё настолько усложняется, что единственно-верным ответом на то, как писать такие приложения и не плодить конфликтов становится использование надёжного кода, что бы никакой дуралей не смог вас обвинить в том, что вы написали дурной код, который не работает в реальной ситуации и Вам не пришлось бы постоянно доказывать, что вы - не индюк или терять авторитет. Что бы можно было всегда чётко разграничить зоны ответственности - это мой косяк, а это - твой.
Вспомнил одну шутку - "можно сделать защиту от дурака, но не от изобретательного!" ![]() Прозрачность и ясность не позволит пользоваться основным для крупных приложений приёмом абстрагирования. Когда вы получаете конструктор и не копаетесь в его внутренностях, что бы понять, как с ним работать - смотрите только на сигнатуры методов и вам сразу же всё становится понятно, как в вашем данном конкретном случае его использовать. Поверьте, в крупном проекте это - недопустимая роскошь - постоянно тратить время на копание в исходниках, что бы досконально знать всю цепочку операторов, которая выполняется при использовании различных сторонних библиотек. Просто каждый разработчик на своём уровне контролирует какой-то "слой" программного кода проекта и отвечает за его правильную работу, всё остальное - не его дело, только так можно масштабировать большие проекты.
Совершенно справедливо! ![]() ![]() Что же касается родной обработки исключений, то объект Error, к сожалению, http://forum.vingrad.ru/forum/topic-38795.html. Ну а насчёт conlsole.log/opera.postError/Debug.write - я думал в сторону http://www.timdown.co.uk/log4javascript/, думаю, его можно настроить на то, что бы он писал сообщения в них.
AKS, пожалуйста, поймите мою позицию - я не зацикленный на Java программист, который любой язык пытается приспособить под паттерны Java. Боже упаси - я рад бы использовать другие паттерны, если они позволят мне создавать действительно масштабные, многофункциональные приложения на клиенте, в браузере. Просто когда я сталкиваюсь с тем, что феноменальная гибкость JavaScript не позволяет ни разработать действительно хорошей среды разработки для сценариев - (по сути, почти всё что я видел не отличается от блокнота с подсветкой, единственное - мне недавно понравился http://www.spket.com/, который использует информацию, содержащущуюся в http://jsdoc.sourceforge.net/, но у него всё равно большое количество глюков), ни написать крупных приложений разными людьми, не очень плотно общающимися между собой, я задаю себе вопрос - "А какого чёрта?". В принципе, нет объективных ограничений у этого языка, которых нельзя было бы преодолеть, вопрос лишь в том, что бы лучше его изучить и правильно использовать для данной задачи. Как правильно? По счастью, я знаком с тем, как реализована Java и я знаю, что её реализация позволяет и создавать удобные среды разработки и писать действительно масштабные приложения сотнями разработчиков, часто вообще не знакомых между собой и находящихся друг от друга на больших расстояниях. Т.е. опыт из Java я привлекаю по мере необходимости. Вы же, насколько я понимаю, пытаетесь обозначить мне потолок - мол, не пиши масштабных приложений, JavaScript разработан для маленьких хитрых скриптиков на страничках - вот для них этот язык используй! Да я использовал - лет 7 уже на JavaScript пишу. Но сейчас мне хочется расширить его возможности по написанию на нём чегото-то серьёзного. Просто другие мои коллеги используют http://code.google.com/webtoolkit/ - слышали о таком? Это компиляция Java-кода в JavaScript - можете себе представить? Я бы хотел всё-таки нащупать реальную альтернативу и выдавить для таких задач все соки из JavaScript, дав людям альтернативные возможности. P.S. А смысла этого метода "isFunction" я чего-то вообще не понял. Можно же просто спросить typeof fn == 'function' - и всё... Впрочем, многое в jQuery и Prototype.js мне так же кажется нелогичным. |
Автор: AKS 6.6.2008, 12:55 | ||||||||
Я честно скажу – я не ведущий разработчик. Поэтому я не имею ни малейшего понятия, как бы я действовал или как надо действовать. Это здесь, в этой теме, пользуясь возможностью, я взялся рассуждать на темы, которые по большому счету меня не касаются. Хотя мало ли чего – может быть, когда-нибудь... Искренне сочувствую. Действительно, в такой атмосфере хочешь или не хочешь, а придется проявить изобретательность (просто, чтобы выжить).
Идеала нет. Следовательно, когда-нибудь кому-то может быть придется анализировать все ваши классы целиком и полностью. Громоздкая иерархия, множество зависимостей здорово осложнит жизнь вашему приемнику (или кому-то еще). Да я все прекрасно понял. Я ведь уже пожелал и удачи, и счастливого программирования. Просто, как я уже написал выше, решил потрепаться. Извиняюсь, что испытываю ваше терпение.
Вот об этом и речь. Не для этих целей был создан, не для programming in the large. Хотя дальновидный создатель языка многое задумывал изначально (это видно из списка 7.5.3 Future Reserved Words), но все это пока не реализовано. А так вспоминается выражение, которое некоторые употребляют на форумах (мне оно не нравиться, но почему-то вспомнилось): "вырезание гланд перректально". Буду внимательно следить за новостями в области ваших разработок. ;) Да Вы что? Как Вам такое в голову пришло? Я тогда уж лучше умолкну... У-у-у! В два раза дольше, чем я! ;) Не-а, не слышал.
Это как раз пример попытки написать "железную проверку" на все случаи жизни. Она такая потому, что предназначается для работы с таким нестабильным, по-разному (местами непредсказуемо) реализованным DOM API. А по-поводу "просто спросить" – спросите у Safari, к примеру, typeof (document.images), и она Вам ласково ответит, что тип есть function. ;) |
Автор: Се ля ви 6.6.2008, 13:11 | ||||||||
В общем, пока неудачно. Попытался загнать эти объекты в свойства конструктора и, единожды создав, потом присваивать его всем экземплярам:
Но после преобразования объявления всех методов по такой методике получилось следующее:
Т.е. функция, будучи связана уже с другим объектом, почему-то продолжает сохранять связь с private-полем того объекта, в котором была создана. Это, надо полагать, объясняется поведением замыкания (closure), благодаря которому и стало возможным использование private-полей и privileged методов:
Ищу решение ![]() |
Автор: Се ля ви 11.6.2008, 17:25 | ||||
Фу-у-у-ух! Дошли! Несколько подзадолбался, честно говоря. Но, вроде, получилось. В итоге вышла довольно-таки громоздкая конструкция, но что делать? ECMAScript такой, какой он есть ![]()
Итак, проблема с предыдущей (будем в дальнейшем называть её простой) реализацией наследования заключалась в том, что, как правильно заметил AKS,
Сразу скажу, я перепробовал массу приёмов и вариантов того, как обойти эту проблему. Если я буду расписывать всё, что я пытался и у меня не получалось, это затянется надолго, по-этому отброшу все свои блуждания, которые в итоге упёрлись в тупик и расскажу только правильный вариант. Итак, проблема в следующем: в Java у нас действительно есть:
Итак, очевидно, что для того, что бы сэкономить на объектах-функциях, нужно хранить их всех в одном месте и ссылаться на них из всех объектов. Лучшим местом для такого хранилища выглядит сам объект функции-конструктора: он всегда доступен из всех объектов по прямой ссылке(чего мы добились в http://forum.vingrad.ru/forum/topic-214727.html). Проблема же состоит в том, что тогда, как было показано в предыдущем посте, эти функции уже не смогут использовать часть контекста - а именно privat`ные переменные. При этом public-переменные функция возьмёт правильные, потому что они окажутся доступны из контекста выполнения - ведь они открыты для использования извне. Значит, конструктор не подходит, нужен другой контейнер функций, хотя то обстоятельство, что конструктор доступен из всех объектов, делает его удобным хранителем ссылки на этот самый контейнер, что бы через него объекты могли связываться с контейнером своих общих функций. Privat`ные переменные доступны только в том контексте, где объявляются - и больше нигде. Так как же нам сделать такой контейнер, который бы их содержал? Думаю, ответ очевиден - наилучшим кандидатом на роль такого контейнера является сам объект! ![]() Но если это будет обычный объект, то мы возвращаемся к изначальной проблеме - у каждого объекта свой набор функций. Так что на этот объект, который будет являться контейнером функций, в силу его специфики, нужно наложить ряд ограничений: 1. Он не должен содержать реальных данных, только никуда не ведущие (null) ссылки private-полей, public-полей не должно быть вовсе. 2. Он не должен инициализироваться (вобщем-то вытекает из первого). 3. У него не должно быть предков (что бы не создавать лишнего экземпляра предка), т.к. методы предков - это те же public-поля, которые будут вызываться по цепочке прототипов от других объектов. Т.е. этот экземпляр в качестве прототипа будет довольствоваться стандартным прототипом по-умолчанию, т.е. объектом с единственной явной ссылкой "constructor" - (кстати по-этому ему не будет нужна ссылка "constructor"). 4. Он должен будет обеспечивать механизм подтягивания в свой контекст из объектов и отправления свойств обратно из своего контекста в объекты. Ссылку на этот контейнер функций, как и говорилось выше, удобнее всего хранить в особом свойстве конструктора. Я предлагаю назвать это свойство "_class", т.к. в некотором смысле, он действительно соответствует классу в понимании ОО-языков программирования, основанных на классах. Так что дальше мы так и будем называть этот объект классом. С другой стороны, если обычные объекты не содержат функций, тогда зачем им хранить private`ные переменные? Кто с ними будет работать? Никто. Так что они в объектах и не нужны. Нужно только обеспечить хранилище для private`ных переменных. Почему бы эти хранилища не содержать в самом классе, спросите вы? Например, в массиве и в объекте содержать только ссылку-идентификатор на соответствующее ему хранилище private`ных переменных? Потому что тогда мы не сможем использовать "сборку мусора" для их удаления - ведь как узнает класс о том, что какой-то объект пользователю уже не нужен и, следовательно, его коллекцию private`ных переменных нужно удалить? Никак. Так что эта коллекция должна находиться в объекте и умирать вместе с объектом. Первое и второе условие реализовать довольно просто - нужно объявлять приватные переменные только в том случае, если объект создаётся первый раз (узнать об этом можно по отсутствию ссылки "_class" у конструктора), в противном же случае создавать public-поля, считывать и присваивать объекту ссылки на функции и затем проводить инициализацию, если это нужно. Третье условие реализуется просто присваиванием ссылке "_class" конструктора значения нового объекта ДО присвоения прототипа классу. Четвёртое - самое интересное. ![]() Этот контейнер я назвал privateState и решил сохранять его в public-поле объекта, к которому он относится. Метод же, который генерирует его, я решил назвать, как нетрудно догадаться, "createPrivateState"... <Продолжение следует... Сейчас надо бежать, допишу сегодня ближе к ночи.> |
Автор: dsCode 11.6.2008, 20:48 |
Се ля ви, не могу не отдать должное за такие подробные блоги о Ваших исследованиях =) Однако, боюсь, вряд ли это столь актуально в ООП языке с динамическими mutable-объектами. Более того, версия 2.0. сулит нам классы (с private'ами, public'ами и т.д.), но классы там будут так же динамическими (хотя, может и будут разграничения на статические и динамические) - все так же можно будет достучаться до прототипов и поменять все, что нужно. Это я к тому, что концепции, подобной строго-статичным классам из Java, C++, PHP здесь не будет. Есть ли смысл притягивать ее сюда за хвост (и очень сильно притягивать - "В итоге вышла довольно-таки громоздкая конструкция") - не знаю (с учетом того, что идеология, описанная в последнем посте - это явный избыток кода, ресурсов, анти-KISS), - не знаю .. (разве, что желание подогнать это под Java-концепции) - Вам решать =) А классы в 2.0. будут в концепции, более схожей с Python'ом (нежели с Java) - там так же, как и в JS (кстати, именно из-за любви к JS, мне очень понравился Питон, хотя до этого (и по долгу службы) на серверной стороне я занимался PHP), можно расширять свойство __class__ инстанцированного объекта (аналог свойства prototype) и новые свойства отобразятся на уже созданных инстансах класса. Приватные проперти там обозначаются двумя подчеркиваниями __private_property и не доступны извне (кроме геттеров / сеттеров) =) по прямому имени, но доступны по косвенному - имя преобразуется в ИмяКласса__private_property (т.е. obj.__private_property упадет по exception'у, что "попытка обращения к приватной переменной", но obj.ИмяКласса__private_property вернет нужное свойство). Кстати, может Вам лучше эту концепцию применить? ;) - сэкономите кучу ресурсов и код упростите (obj.getA() будет работать, но obj.a - уже нет). Просто не понятно - от кого эти защиты? От программистов, которые будут использовать Вашу библиотеку псевдо-классов? Так они и так умеют программировать и понимают, что к чему. От программистов, которые отрицают концепции прототипного программирования? Не знаю... может им есть смысл увидеть разницу этих подходов и взять на вооружение оба. Ну а иначе, кроме как спортивного интереса подогнать идеи JS под статично-классовые концепции, я не особо вижу. А Рич-Эпликейшенс (и подобные GMail, которые Вы приводили) пишутся и без имитаций private'ов. P.S.: тем не менее, не хочу сказать, что Ваши труды напрасны =) За один только объем статей можно руку пожать ) |
Автор: Се ля ви 12.6.2008, 01:58 | ||
Итак, мы остановились на объекте privateState. Что бы он сохранял в себе информацию достаточно надёжно и не позволял получить к ней доступ извне никому кроме класса, данные в нём должны так же содержаться в private`ных переменных. При этом этот объект должен сам отвечать за выгрузку и загрузку этих данных в класс, т.е. должен иметь доступ к private`ным переменным класса. Как же этого добиться? Очень просто - генерировать его в таком месте, где он сохранит ссылки на private`ные переменные. Т.е. нам необходимо создать функцию генерации privateState`а внутри класса (и эта функция единственная не должна потом быть доступна из объектов). Для выгрузки и загрузки данных этот объект должен обладать 2-мя методами. Я назвал их незамысловато - "_set" и "_get". Всё просто - "_get" присваивает переменным класса значения переменных, соответствующих экземпляру объекта, а "_set" присваивает переменным объекта значения переменных класса и возвращает тим ссылкам их значения null. Таким образом, каждый private-метод имеет смысл начинать с вызова this.privateState._get(); и заканчивать после вызова this.privateState._set(); с единственным условием - this.privateState._set(); должен быть вызван везде перед выражением "return". Для тех же методов, которые возвращают не пустой результат, соответствующий private-полю, придётся делать дополнительные действия, создавая новую переменную (обычно в этом случае она называется "result"), записывая в неё нужное значение, затем вызывая this.privateState._set(); и только после этого записывая return result;. Так же необходимо вносить вызов _set в случае возбуждения исключительных ситуаций (перед ключевым словом throw). Для того, что бы вызов одними методами других методов внутри класса не вызывал постоянной перезаписи ссылок из privateState, необходимо внести так же переменную-счётчик вложенности методов в объект privateState - "_inner", что бы объект выполнял какие либо действия исключительно последовательно - вызвали метод "_get" первый раз - переменные подгрузились, счётчик принял значение 1, второй раз - просто присвоили счётчику значение 2, и т.д. Когда вызываем метод "_set", происходит проверка и декрементация счётчика - если это единица, ссылки копируются обратно в объект, иначе - кроме декрементации счётчика метод не делает ничего. Типовая реализация будет выглядеть примерно так:
На последок маленький совет. Думаю, что разрабатывать какой-то новый функционал будет заметно удобнее с использованием простой модели наследования, а уже потом, когда всё в порядке, только не устраивает объём съедаемой памяти и скорость выполнения, можно переделывать её в эту, заметно более сложную модель. |
Автор: AKS 12.6.2008, 07:59 | ||||
dsCode, это интересно! Но не понял, как реализовать то, что я подчеркнул?
Се ля ви, а у меня другой результат. Вы уверены в том, что не "напортачили"? |
Автор: dsCode 12.6.2008, 13:06 | ||||
AKS,
Да не настолько и интересно =) - обычно. Ведь свойство, по сути, не является приватным (нет, лучше не так, а: вообще нет никаких приватных свойств, а вся приватность скрыта за согласованием именования). И я, естественно, говорил о простых (и единственных - public) свойствах. В Питоне, как я уже отмечал, это сделано преобразованием всех свойств с двумя подчеркиваниями к виду _ИмяКласса__имя_приватного_свойства (кстати, по официальному programming style guide'у, в Питоне имена свойств должны обозначаться через подчеркивание (в C-стиле), а не "верблюдом", как в Java и JavaScript - поначалу очень раздражает =)), но не суть =)). Пример (на синтаксис можно не обращать внимания):
Как это сделать в JS - без разницы. Можно просто назвать __a (и тогда obj.getA() { return this.__a; }, работает, а obj.a - нет (в смысле, не "нет", конечно же, а undefined, мы ж о JS говорим)) P.S.: у меня ощущение, что я написал много лишних ненужных слов (когда коротко: для обеспечения легковесных объектов (особенно для Rich-Applications) нам не нужны замкнутые var'ы для имитации именно private'ов из статично-классовых языков, а можно обойтись именованием), но тем не менее =) Се ля ви, ну получилось, что второй инстанс ссылается на методы первого (f1.setPrivateField1 === f2.setPrivateField1), но ведь в виду замыкания и состояние будет тем же, поэтому утверждение alert('f1.getPrivateField1() = ' + f1.getPrivateField1()); //'f1.getPrivateField1() = 2' === false (т.е. не 2 там будет, а 3, т.к. Вы сами меняете состояние f1, вызовом f2.setPrivateField1(3) строчкой выше). |
Автор: Се ля ви 12.6.2008, 14:09 | ||||
Народ, чуть-чуть обшибся ![]() Надо строчку "var _inner = true," в методе this.createPrivateState заменить на "var _inner = 0,". В предыдущем посте я уже поправил - теперь всё в порядке, нормально работает.
Да нет же, в том-то и весь фокус, что бы состояние соответствовало каждому объекту, иначе ничего не получится. Для этого я создание объекта-состояния и поместил внутрь класса - что бы замыкание позволило ссылаться и на переменные данного состояния и на переменные класса (переменные состояния в точности повторяют имена переменных класса, только вначале содержат два символа "_" в отличие от обычных переменных). Всё работает чётко, просто я сначала сделал флаг, а потом подумал про вложенные вызовы методов и решил изменить на счётчик, только там где у меня инициализировался флаг я забыл заменить его нулевой счётчик. ![]() В общем, всё в порядке. ![]() |
Автор: Се ля ви 12.6.2008, 15:09 | ||||||||||||
Я описывал чуть выше - весь мой опыт работы в крупных проектах свидетельствует, что без разделения кода на зоны ответственности эффективная кооперация большого количества разработчиков практически невозможна. В итоге максимум, на что можно рассчитывать - это код, который пишут 2-3 человека, из которых один главный, а эффективность работы остальных сильно снижена из-за того, что они должны постоянно разбираться в коде первого что бы что-то к нему добавлять. Как раз на разделение зон ответственности направлен ряд приёмов в Java, которые я и хочу сюда привнести.
К сожалению, я не могу с вами согласиться. Т.е. если это и сделают, то очень не скоро их использование станет стандартом де-факто. Да за примером далеко ходить не надо - вы же сами пишете:
значит, вы занимались PHP, а какой, позвольте спросить, версии? Ваш переход на Python состоялся до или после выхода PHP 5, в котором появились довольно-таки хорошие ОО-средства работы с кодом? Я, конечно, не в курсе ваших мотивов, но, к сожалению, Python тоже пока весьма слабый игрок на поле крупных корпоративных приложений (хотя многие надеются, что со временем станет) - и брать с него пример по-этому в таких вещах несколько преждевременно - он ещё не настолько успел себя зарекомендовать. Так же и с JS 2.0 - у меня большие сомнения, что его моментально реализуют и он моментально распространится по 90-95% аудитории интернет-пользователей. А пока не настанет такой момент, его использование будет просто забавой программистов на досуге - не более того, ибо ни один денежный заказчик с этими технологиями связываться не рискнёт. По моим прогнозам, после принятия этого стандарта до этого момента пройдёт года 3 - не меньше. Эти три года вы предлагаете лишь мечтательно воздыхать в предвкушении? Если так - не имею возможности составить вам в этом компанию ![]() Когда вышел PHP 5 и где рынок хостеров, наперебой предлагающих размещать у них сайты, написанные на нём, по-дешевле? Сколько их сейчас и как быстро появлялись те, что есть? Нет рынка - считай, нет и технологии, есть только игрушки... Ну а насчёт строго-статичных концепций - я не очень в курсе про С++ и PHP, но в Java есть такая штука - Reflection API - которая позволяет работать с классами как с mutable, делая с ними всё, что угодно - считай, если бы все программисты постоянно использовали Reflection API, эта ситуация во многом бы и напоминала реалии программмирования на JS. Просто при программировании на Java не принято использовать Reflection API, он используется только для специфичных задачь, например сетевыми взаимодействиями и инструментальными средствами для облегчения разработки. Примерно тот же подход я предлагаю внести и сюда - некоторое соглашение, что мы не используем без крайней на то необходимости метод "eval" и динамическое создание/удаление/изменение методов, а так же стараемся по-минимуму использовать public-поля и, если уж используем, то с сохранением их типа и других соглашений в отношении них.
Эти защиты, т.е. инкапсуляция, делаются для того, что бы упростить интерфейс класса, дав пользователю библиотеки доступ только к тем методам и полям, которые сделаны для него и ник каким другим. Что бы он мог полностью абстрагироваться от реализации и думать о решении задачи в тех терминах, в которых ем предлагается о нём думать. Потому что реализация - это не его зона ответственности, за это отвечают другие - а именно - создатели этой библиотеки.
Ну, во-первых здесь не имитация private`ов, а самые настоящие private`ы - попробуте-ка обратиться к свойству "_privateField1" напрямую - ничего не выйдет, оно замкнуто - так что это самое настоящее private-поле, никакой "имитации". Просто реализовано оно через замыкание. А по поводу RIA - я свои аргументы изложил. Я не знаю, как писался GMail, но учитывая что таких приложений не очень много, могу предположить, что обошёлся этот проект достаточно дорого и потребовал привлечения хороших профессионалов, которых под силу найти, обучить и сбить в дружный коллектив могут себе позволить немного компаний. В моём понимании проекты подобного масштаба удобнее писать большими командами, а разработчики - это люди со своими проблемами и заморочками и без изоляции одного кода от другого и чёткого разграничения ответственности чаще всего далеко не уедешь.
Вот этот-то подход и оборачивается злом в больших проектах. Реальность такова, что заказчик, на которого давят конкуренты, будет давить на вас со сроками и постоянными непредсказуемыми изменениями в проекте, и что бы под этим давлением отстаивать нормальную, стройную архитектуру приложения, нужно иметь железные нервы и большой авторитет в глазах заказчика, которых чаще всего нет. В итоге либо вы, либо кто-то другой не выдержит и начнёт использовать "грязные" приёмы программирования - т.е. эксплуатировать слабые, не защищённые с концептуальной точки зрения, места в технологиях на уровне их реализации - например, в данном случае, зная об этом именовании, слабовольные программист начнёт активно работать с этими значениями напрямую и плодить в будущем трудноуловимые баги, которые хорошо если ему попадётся исправлять и он ещё будет помнить, что он делал, а если они достанутся кому-то, кто придёт после него, когда он уже уволится и его уже это не будет касаться? В итоге через несколько лет разработка больших проектов превращается в настоящий кошмар, где без рефакторинга ничего не сделаешь, а разоряться на рефакторинг никто не будет... |
Автор: dsCode 12.6.2008, 17:17 | ||||||
поправка - занимаюсь и в данный момент (хотя, в последнее время - больше фронтэндом, а именно - JS), по версиям - начиная с 3-ей и до текущей 5.2.6. А Питоном я занимаюсь только "на досуге" (пока).
Мотивы? Всего лишь показал схожесть с JS и, как там реализованы private-свойства. Питон идет в составе любого нормального дистрибутива Linux. Кстати, Java мне также очень нравится, поэтому не подумайте, что я собираюсь тут холиворить о том, какой язык лучше. Я вправе использовать все, какие потребуются [с учетом их специфики для достижения оптимальных результатов]. Я в курсе =) и в PHP она достаточно хорошо организована. Более того, в Питоне эта "штука" тоже есть. Анализ-то - понятно, но скажите, как при помощи рефлекции (или интроспекции) можно добавить новое свойство / метод в класс и, чтобы это свойство отобразилось на всех уже инстанцированных объектах? [я просто не использую ее каждый день, и не слышал, что так можно ![]()
Кто такой слабовольный программист? Зачем он начнет это делать? Вот это, как раз-таки, игрушки. Если ему так это захочется, он пойдет, переименует свойство в public и будет менять его дальше напрямую. Нет-нет, я знаю, что такое инкапсуляция и зачем она нужна. В разных языках - разные подходы, и Питон я приводил лишь для примера альтернативного подхода - пусть "псевдо-инкапсулируещего", но тем не менее. И это не значит, что я против инкапсуляции в Java, PHP, C++ - вовсе нет, не делайте неверных выводов. Ах, пардон, я не заметил return new function() {. Ну, т.е. получился wrapper который должен дублировать имена приватных свойств (рабочих приватных свойств) в своем скопе (только с двумя подчеркиваниями). Каждый объект имеет собственные свойства getPrivateField1 и setPrivateField1 (и т.д.) - пусть это теперь не "цельные тяжелые куски", а лишь ссылки на методы контейнера, но все же - они свои, в то время как семантически одинаковым методам место в прототипе (но здесь получается выигрыш в скорости - посредством делегирования медленней получить результат, чем вызвав свой метод, который "под боком", но проигрыш в ресурсах). Только я боюсь, что кто-то скажет (когда увидит вот это в каждом методе):
"Блин, а ... никто не знает, зачем в каждом методе здесь вот эти две строчки всегда надо писать? Инкапсуляция? Да ладно?... Хм.. Оригинальный подход..". Не кажется ли Вам, что, забудь все тот же "слабовольный программист" (Ваше определение) написать загрузку этих инкапсулированных строк, он еще больше трудно-уловимых багов наделает? P.S.: еще раз, тем не менее, я в состоянии оценить проделанную работу. ![]() |
Автор: AKS 12.6.2008, 17:23 |
Се ля ви, да, действительно, теперь в порядке! Стал разбираться с кодом – появился все тот же вопрос. Вы ушли от более простого к более сложному. Если раньше в теле конструктора для каждого экземпляра создавалась пара геттер/сеттер для доступа к локальной переменной, то теперь для каждого экземпляра создается объект privateState плюс все та же самая парочка геттер/сеттер, являющаяся теперь методами этого объекта. Вопрос в следующем – Вы просто смирились с тем, о чем писали ранее: "тут получается, что я расточительно создаю каждый раз новый объект-функцию"? Ну и еще не могу удержаться, чтобы не задать буквально парочку вопросов. У Вас сейчас _super – это св-во глобального объекта. Каждый конструктор в момент вызова будет присваивать ей новое значение. Это так хитро задумано? ![]() ![]() dsCode, я просто дословно воспринял это ваше "obj.getA() будет работать, но obj.a - уже нет". Подумал, как же так сделать, чтобы obj.a было доступно через obj.getA(), и никак иначе. ![]() |
Автор: dsCode 12.6.2008, 17:37 | ||
объект, который содержит скоп всех верхних "породителей" (ведь и _privateField1 и _privateField2 оседают в скоп-чейне медотов this._get и this._set (которые всегда свои для privateState'a), так?). И помимо этих двух геттеров / сеттеров, есть еще и ссылка-геттер, и ссылка-сеттер, которые хоть и ссылки, но имеют значения, а значит - "жрут ресурсы". |
Автор: AKS 12.6.2008, 17:53 |
Я их и имел в виду, написав "парочка". Т.е. я все по-прежнему, с начала темы, обращаю внимание именно на объекты (раньше были функции, теперь объект плюс его методы), которые будут создаваться всякий раз для каждого экземпляра отдельно. |
Автор: dsCode 12.6.2008, 18:01 |
AKS, да это-то я вижу =) Я просто хотел поинтересоваться про скоп-чейн, который хранит все эти замкнутые var'ы. Скоп внешней функции приплюсовывается к скопу внутренней при ее создании. Получается, что и this._get и this._set имеют скоп с var'ами _privateField1 и _privateField2 (поскольку используют эти var'ы). В то же время, privateState всегда новый (в виду new). Получается куча этих скопов, связанных с this._get и this._set будет жить при создании нового объекта (или там ссылка на, может быть, какой-то общий скоп-чейн?). |
Автор: AKS 12.6.2008, 19:06 |
dsCode, там получается следующее. Сначала идет вот это: F._class = new F;. В момент первого вызова конструктора F выполняется та часть его кода, которая в else. Все, что там создается - там же и остается. Все методы хранят ссылку на variable-объект, созданный в этот момент (момент первого вызова F). Переменная _privateField1 - одна на всех. Ее поиск будет осуществляться так (VO - это variable-объект функций): _get VO, VO анонимной функции, createPrivateState VO, F VO (тот, что был создан в момент первого вызова). |
Автор: dsCode 12.6.2008, 19:40 |
AKS, ага... Т.е. на момент первого вызова (фактически - на момент определения _get / _set, которые используют _privateField1 из скопа F), будет однозначно определена цепь скопов, по которым будет производиться поиск _privateField1 при вызове _get - не зависимо от того, что this.createPrivateState всегда возвращает результат новой анонимной функции. Эмм.... Ну да, в любом случае _privateField1 принадлежит VO(F) и будет искаться в нем. Спасибо =) |
Автор: Се ля ви 12.6.2008, 23:54 | ||||||||||||||||||||||||||||||
dsCode,
А, ну тогда вот это прямо по адресу:
Это замечательно. Я, честно говоря, тоже немного сталкивался с Python`ом и тоже испытываю к нему симпатию. Особенно нехватает в Java его фишки с блоками с помощью табуляций - настолько это было бы удобно!.. Хотя вообще-то, больше всех мне нравится язык SmallTalk - это вообще божественный язык программирования, жаль, что ни одна крупная корпорация не взялась его продвигать и писать под него библиотеки на все случаи жизни, как содружество Java Community Process для Java, Microsoft для C# и Borland для Object Pascal... :( Но это уже совсем другая тема.
Я не очень подробно занимался этим вопросом. Я слышал, что это реализуется на этапе подгрузки класса - можно переопределить ClassLoader и при подгрузке класса в JVM поменять в нём всё что угодно. Однако про такие приёмы, как изменение свойств класса, что бы они отразились на уже созданных экземплярах я не слышал - но, возможно, есть и такие, я просто не достатьчно подробно занимался этим вопросом. Можете спросить об этом в соответствующем разделе Vingrad`а - у нас по Java очень сильные специалисты.
Не столько в этом, сколько в том, что теперь объект-функция (ведь каждая функция - это объект) теперь создаётся один раз и все объекты просто на неё ссылаются - на одну и ту же. Я сначала хотел, действительно, реализовать их через прототип, но тогда я не смог бы продолжать цепочку наследования дальше - т.е. не смог бы обеспечить каждый объект F своим собственным экземпляром E - поскольку прототип у класса был бы один и нельзя было бы реализовать скозную (через класс) связь объекта с объектом предка - задача, с которой началась эта тема. Так что пришлось реализовывать так.
Фактически, это - паттерн. Обычно в комментариях указывается, что "это и то было реализовано с использованием паттерна такого-то...". И что бы разобраться в чужом коде, если натыкаешься на упоминание о паттерне в комментариях, очень полезно бывает посмотреть описание этого паттерна в каталоге паттернов - тогда все вопросы о страннастях кода отпадают. По крайней мере так принято в мире Java. Здесь так же может помочь только этот подход.
Совершенно верно! По-этому я и говорю:
AKS,
Да, теперь это просто пара ссылок на существующие вне объекта методы.
Почему же смирился? Как раз наоборот - каждая функция-то теперь одна единственная, на неё только ссылок много, но ссылки памяти практически не занимают, в отличие от объектов, на которые они ссылаются. Представьте, что у вас 100 000 экземпляров одного конструктора с одной парой геттер/сеттер. Если он реализован в простой модели, то это означает, что у нас появляется ещё 100 000 объектов-функций геттеров и 100 000 объектов-функций сеттеров. А в данной модели 100 000 ссылок на геттер и 100 000 ссылок на сеттер - что будет тяжелее обработать браузеру? Это, конечно, зависит от того, насколько хорошо реализованы ссылки в браузере, но думаю, что объём памяти, используемой при таких раскладах в первом и во втором случае просто несоизмерим - второй подход тут просто единственно-возможен.
Это ошибка, спасибо, что подсказали. Действительно, нужно поставить var, уже исправил. Кстати раз это private-поле, то хранить его нужно в объекте privateState - а тогда и передавать его туда нужно параметром для правильной инициализации, что я и сделал:
и потом:
Это проверка, переданы ли аргументы. Просто инициировать объект сразу после создания имеет смысл лишь тогда, когда ему был передан хотя бы один аргумент - иначе это означает, что объект создаётся для будущего наследника. Просто в нашем простом случае у нас так и так нету параметров, так что и в случае отсутствия даже одного первого аргумента мы тоже должны вызвать метод "init". Так что первая строчка "для порядку" - что бы когда кто-то будет copy/paste`ить этот код, то ему было просто удобнее вписывать свой код в те места, где сейчас по сути заглушки ![]() |
Автор: dsCode 13.6.2008, 00:59 | ||||
Се ля ви, В http://ru.wikipedia.org/wiki/Python ООП модель заимствованна именно из SmallTalk (хотя на Смолтоке я ни разу не программировал - не доводилось (как ни странно - даже ради интереса), но, уверен, что мне бы тоже понравился (в виду того, что он является и прародителем JavaScript'a). Может, как-нить гляну на досуге). Кстати, есть реализация Jyton - реализация Питона на Java, выполняющаяся через JVM, таким образом есть возможность использовать классы Java. Ну ладно, не о Питоне речь - опять отвлекася =)
Не сомневаюсь, что сильные, да только мне еще сильнее "кажется", что нет такого ни в Java, ни в C++, ни в PHP (но есть в JavaScript, Python, Ruby (скорей всего, не проверял) и Smalltalk (также интуитивно предполагаю)).
Так принято в любой нормальной системе. И я имел в виду, что "паттерн" этот будет слишком избыточным в коде (постоянное присутствие этих двух строк) и в итоге, все равно, сжирающим память. Ну а теперь обратно к вопросу (и AKS правильно подметил): пусть getPrivateField1 теперь будет ссылкой, но createPrivateState у каждого объекта свой (вы же сами меня поправили, когда я не досмотрел, что состояния у объектов - свои), и ввиду return new function() { ... } так же будут свои _get и _set - т.е. остается все та же "парочка" (только она теперь в другом месте - внутри createPrivateState), плюс к этому две ссылки - getPrivateField1 и setPrivateField1. |
Автор: Се ля ви 13.6.2008, 02:16 | ||||
Да и PHP есть реализация и вообще в JDK6 есть механизм скриптинга - можно множество языков реализовывать и использовать.
Насчёт createPrivateState - что значит - у каждого объекта свой? Это метод класса, который просто возвращает нам объект. Этот объект у каждого свой, но метод-то один ![]() Ну да, согласен, у нас появляются пара _get и _set для каждого объекта и в случае с одним полем это выглядет одинаково, но одно поле - это только для демонстрации, подразумевается-то, что у каждого объекта по многу свойств и, соответственно, много пар геттер/сеттер и тогда при таком подходе мы меняем всё это множество методов на одну реализацию методов и _get с _set`ом для подмены контекста класса. Выигрыш будет именно при наличии большого количества методов. |
Автор: AKS 13.6.2008, 06:03 |
Се ля ви, я, конечно, не очень много видел в своей жизни, но хотелось бы написать, что такой конструкции не встречал. Получилась не плохая тема для блога (Вы ведь, вроде, хотели статью писать). Теперь единственное, что может напугать, так это: "подразумевается-то, что у каждого объекта по многу свойств". Т.е. если будет много св-в, то конструктор будет выглядеть громоздко. Но это, наверняка, можно обойти, используя, например, массивы или еще как-нибудь... Ну а в общем - мне нравится (хоть и не испытывал толком, но зато уже "привык" к ходу вашей мысли ![]() |
Автор: AKS 13.6.2008, 06:47 |
Забыл про проверку аргументов написать. ;) Вот это: if (arguments[0] != null) не проверка "переданы ли аргументы". Это проверка "нет ли у первого аргумента значений null/undefined". Поэтому и спросил - это дизайн такой на запрет передачи подобных значений или еще что-то. |
Автор: dsCode 13.6.2008, 11:11 |
аай, ну это уже опечатка (Copy-Paste'ная) - конечно, я имел в виду объект privateState, а не метод, который его создает. угу, ну это, действительно, радует =) Се ля ви, ну а в общем - мне тоже нравится в плане реализации private'ов в стиле Java (как я уже говорил =)). Anyway, хранение всех этих семантически одинаковых геттеров / сеттеров в прототипе, наличие псевдо-инкапсуляции, и отсутствие в каждом методе этих двух строк по загрузке / выгрузке privateState'a, - будет быстрее (замкнутый var всегда будет искаться дольше в цепи, чем this-свойство) и легче (и по ресурсам и по трудозатратам). А вот при какой реализации будет больше трудноуловимых багов, - это надо еще проверить, и, я думаю, вы еще напишете об этом в своей статье =) |
Автор: Се ля ви 16.6.2008, 14:10 | ||||||||||||||||
Да, спасибо за поправку. Изменил на
Ещё нововведение - наследование решил снести внутрь объявления класса:
![]() Чуть по-сложнее, но тоже оказалось возможным избавиться и от другой неудобной строчки:
![]() Второй заяц, которого мы убиваем этим приёмом - теперь мы создаём класс только в момент создания первого его экземпляра, таким образом в случае, если нам объект не понадобится, мы сэкономим пользователю память. Ну и некоторые редакторы на "super" ругаются - вроде как зарегистрированное слово, хотя браузеры проглатывают. Не будем искушать судьбу и аргумент функции "createPrivateState" назовём "super_", а то - мало ли что? В общем, финальный вариант кода:
|
Автор: Се ля ви 16.6.2008, 15:08 | ||
Думаю, лучше всего решить это инструментальными средствами - в средах программирования или обфускатором-оптимизатором JavaScript-кода - надо только присмотреть наиболее удачный и расширяемый и дописать к нему эту функциональность. В итоге, я думаю, можно будет написать объект в простой схеме наследования и она автоматом при оптимизации преобразуется в лёгкую (про себя вторую я называю так). Я уже продумываю этот момент ![]() |
Автор: Се ля ви 22.6.2008, 19:08 | ||||||||||||||
Несколько улучшений для лёгкой модели: 1. Вызов метода init:
Т.е. я предлагаю считать заявленные непосредственно в конструкторе параметры обязательными, а все параметры сверх - необязательными и с этих позиций мы вызываем метод init, передавая ему все параметры, переданные конструктору, только в случае, если параметров передано не меньше, чем заявлено в объявлении конструктора. 2. В методе init вызов метода init для предка производим так:
3. Избавляемся от постоянных строчек "this.privateState._get();" и "this.privateState._set();" в каждом public методе, необходимости всякий раз объявлять какую-либо переменную (например, "result") и присваивания ей значения private`ного поля перед его возвратом (для выполнения "this.privateState._set();"), а так же выбрасываем логику подсчёта вызовов в объекте privateState за счёт создания специального private-метода
и одной строчки вызова его в начале каждого public метода, которую предлагаю помещать сразу после открытия блока функции дабы она не мешалась с основной логикой метода:
, т.е. всю работу по взятию и сохранению данных из privateState за нас выполняет этот метод, а наличие или отсутствие экспортируемых данных определяется по переменной _super, которая должна быть у любого объекта всегда (если он ни от кого не наследуется, тогда она должна принимать значение Object.prototype:
В общем, работающий код лёгкой модели:
И всё бы хорошо, но... есть проблема:
Т.е. теперь ни класс, ни объект не распознаются оператором instanceof как экземпляры конструктора F... Нехорошо... ![]() У кого-нибудь есть по этому поводу какие-нибудь предложения? |
Автор: dsCode 22.6.2008, 22:45 | ||
Се ля ви, у вас там ошибка в строке "return new this.constructor().init.apply(null, arguments);" (FireBug выдает), поэтому пока особо не вникаю, пофиксите. По поводу instanceof (тоже при беглом просмотре) - в 18 строке (this.constructor.prototype = new this.constructor.prototype.constructor;) вы подменяете прототип конструктора. Поэтому (new F).__proto__ уже не сравнивается с прототипом F, но сравнивается с прототип E (и даже прототипом Object):
P.S.: и все-таки (повторюсь в 10 раз - из-за особенностей языка), больше подошло бы "optimized heavy model of ..." |
Автор: Се ля ви 22.6.2008, 23:19 | ||||||||
Да, я уже пофиксил, просто не пойму, как можно конструктору объекта пареметры передать из arguments? При вызове обычной функции fnName.apply(this, arguments) работает, а вот если перед этим появляется ключевое слово new, возникает ошибка...
Да, я как раз только что копал реализацию http://www.mozilla.org/rhino/ (это как раз реализация ScriptEngine от разработчиков проекта Mozilla). Нашёл вот это:
, а jsDelegatesTo реализован вот как:
Мда... Но я не могу иначе - мне обязательно нужно создавать отдельный прототип для каждого объекта наследника, иначе как сделать так, что бы внутреннее состояние (private переменные) для каждого объекта наследника отличались? Ладно, буду ещё думать... Добавлено @ 23:27
Хм, ну не знаю... Я хотел подчеркнуть, что одна модель простая - simple - т.е. лёгкая для понимания и модификации со стороны разработчика, а другая - light - лёгкая для браузера. А heavy - это как раз "тяжёлая", сразу интуитивно возникает вопрос - зачем она акая тяжёлая вообще нужна? И дискомфорт даже какой-то смутный... |
Автор: dsCode 23.6.2008, 13:11 |
Еще пару ошибок нашел: - apply init'a не вызывался, потому что в 81 строе вы сами его уничтожаете (но дальше там опять ошибки пошли, после того, как init вызвался - в общем, надо еще смотреть, где там что теряется); - this.constructor.length всегда будет 0 (и что это вообще? когда это свойство будет ставиться?); Да, instanceof в любом случае работает с прототипами конструкторов, а вы его (прототип) подменяете. так я и говорил с легким сарказмом (с легким! ;)), чтобы еще раз сказать, что JS и так-то в 50-200 раз медленней Си и Java в виду своей нынешней ООП-модели и интерпретируемости, а тут он еще медленней становится (ну понятно, что для обеспечения привычного вида инкапсуляции, но я убежден, что в нынешней реализации JS это излишне). Поэтому все-таки это "хэви", но уже в разы оптимизированная (в виду хранения гетторов / сеттеров в одном объекте) "хэви" (что, естественно, радует). |
Автор: Се ля ви 23.6.2008, 14:09 | ||||||||||||
Да, надо ещё тестировать, но чесно говоря, уже лень - я альтернативную модель сейчас делаю, с тем, что бы всё-таки заюзать прототипы вместо классов. А эту реализацию развивать смысла нет, потому что instanceof, на мой взгляд, терять нельзя. К тому же действительно новая модель получается более элегантной ![]() Скоро всё будет.
Я написал достаточно подробно про эту фичу:
Дело в том, что я этот код пишу не только для демонстрации, но и для copy/paste`а, так что многое может показаться излишним, просто задача решается в общем виде. this.constructor.length - это количество параметров инициализации, передаваемых конструктору (который сейчас выполняется) - у любой функции есть такой метод. В данном случае это - ноль, но если кто-то будет использовать этот код, у него наверняка параметры будут и это не будет ноль, так что ноль вместо этой конструкции я поставить не могу.
Да, "понимание приходит с опытом"... Жаль. Придётся существенно переделывать модель. Но у меня есть ощущение, что она от этого только улучшится и упростится ![]()
Хорошо, убедили. Пускай будет Heavy JavaScript inheritance realization (HJSI) и Light JavaScript inheritance realization (LJSI). ![]() |
Автор: dsCode 23.6.2008, 14:39 | ||
угу, с удовольствием посмотрю (кстати, http://www.netlover.h11.ru/programming/lessons/jsoop.php реализация, имитирующая классовое ООП в JS (с наследованием, тыры-пырами и т.д.). Там есть недочеты, но это старый код (сейчас уже переписан); основные моменты для работы там реализованы (по крайней мере, мне хватало, чтобы сделать систему объектов и связать их. Хотя, если честно, все это тоже накладывает расходы ресурсов). С this.constructor.length я и так все понял до этого, я просто спрашивал - когда он установится? - и метод и свойтво =) а я вот запамятовал )) (мне причудилось, что кол-во аргументов можно только из arguments посмотреть) |
Автор: Се ля ви 24.6.2008, 00:47 | ||||
Итак, представляю вашему вниманию тяжёлую модель реализации наследования. Это не конечная реализация, она нуждается в некотором улучшении и, возможно, дополнении. Но пока она представляет вполне работающий вариант. В основе лежит следующая схема:
При этом методы хранятся в прототипах, им же передаются privateState`ы от объектов во время выполнения их методов по уже показанному ранее механизму - в этом плане ничего не изменилось, просто вместо явной ссылки _class теперь используется неявная ссылка __proto__. Что же касается объекта-предка, то теперь он доступен только по private ссылке _super у каждого объекта. При этом специальный блок инициализатора объекта (он находится в методе createPrivateState) присваивает ссылки на все его не перекрываемые открытые поля и функции, создаваемому объекту (при этом мы специально передаём ссылки на методы объекту, а не прототипу для того, что бы иметь возможность применять эту модель не только для конструкторов, в которых уже реализована эта модель, но и для любых других - что бы не было необходимости переписывать уже созданный код). К сожалению, модель ещё менее удобна для copy/paste`а - как вы сейчас увидите, в ней в нескольких местах придётся проставлять имя объекта, от которого мы наследуемся и имя самого объекта, а так же, что является старой проблемой, в нескольких местах необходимо дублировать имена private полей. Над этим я сейчас работаю. Итак, пока прошу любить и жаловать:
|
Автор: Се ля ви 24.6.2008, 10:52 | ||
И лёгкая модель, с которой работает операция instanceof:
Как видим, результат идентичный кроме строчки вывода 'f1.setPrivateField1 === f2.setPrivateField1 = false', в которой мы чётко видим, что теперь у каждого объекта свои методы, которые при увеличении их количества гораздо быстрее будут забивать всю память, чем просто данные. Тем не менее, эта модель проще и удобнее для написания библиотек руками. В будущем я думаю создать специальный инструментарий как расширение к компрессору (пока первый кандидат на расширение - http://developer.yahoo.com/yui/compressor/, хотя предложения принимаются - для обсуждения этого вопроса создана http://forum.vingrad.ru/forum/topic-217787.html), который будет преобразовывать LJSI-конструкторы в HJSI, позволяя разработчику не думать о сложностях реализации HJSI-модели. Ну и, безусловно, время от времени будут вноситься изменения с целью упростить обе модели. Так же на очереди:
![]() |
Автор: dsCode 24.6.2008, 12:11 |
Щас времени нет особо, позже напишу. Се ля ви, я ж саркастически утрировал (писал уже об этом) =)) Зачем такое название? (просто как-то буквально вы это название восприняли) )) Сами говорили: |
Автор: dsCode 24.6.2008, 23:39 | ||||
хех =) поначалу хотел спросить, зачем двойное присвоение this.constructor.prototype (строки 15, 16), потом увидел, что первый для ветки if (this.constructor == A) { ... } Есть проблема "холостых выстрелов" (поставьте alert(...) в конструкторе B и создайте один объект - по вашей схеме проалертит три раза, в то время как конструктор должен срабатывать при создании пользовательского объекта (а не вспомогательных того же "класса" внутри реализации библиотеки)):
Вижу (строка 50), что проперти "класса" А копируются в создаваемый объект - т.е. возьмутся не из прототипного объекта (который должен быть экземпляром "класса" А (ну, или wrapper'ом - для избежания "холостых выстрелов")), а будут ownProperty. В общем, сказать, что это перегруженная и сложная для понимания модель - это практически ничего не сказать (уж не обессудьте)! =)) Да я и изначально говорил, что из-за одного спортивного интереса сымитировать концепции Java, так усложнять и напрягать JavaScript не стоит) =)) |
Автор: Се ля ви 27.6.2008, 15:04 | ||||||||||||
Да. Сейчас я немного исправил - я убрал оттуда ссылку на предка и поставил проверку на равенство реально конструктора свойству "constructor" - так по крайней мере не придётся несколько раз упоминать в коде имя конструктора-предка. Но всё равно остаётся проблема, что при copy/paste придётся вписывать имя конструктора в трёх местах, что не очень удобно. При чём, что самое неприятное - от этого не удаётся избавиться даже в лёгкой модели... :( Буду ещё думать.
Почему же, никаких холостых выстрелов! При вызове первый раз конструктор тягается 3 раза.
Да. Этим я как раз и добиваюсь того, что бы у каждого объекта-потомка был свой объект предка и при изменении состояния предка (private-полей) они менялись только для данного объекта, а у остальных таких же объектов объекты предков оказывались бы "не повреждёнными". Этим действием я добиваюсь, что все открытые (интерфейсные) элементы предка получает потомок, но не через прототип, а напрямую. Дело в том, что эта модель наследования должна уметь работать не только с конструкторами, сделанными по ней же, но и с любыми другими альтернативными моделями наследования и даже простыми конструкторами без наследования, где и методы, как и public-поля могут оказаться частью объекта, а не прототипа. По-этому, раз использовать экземпляр предка в качестве прототипа я не могу, мне нужно подтягивать их напрямую ![]() |
Автор: Се ля ви 28.6.2008, 00:23 | ||
Удалось избавиться от упоминаний предка и данного класса во многих местах - теперь и то и другое упоминается лишь в одном месте, таким образом copy/paste`ить стало земетно легче.
Пока не решены проблема с несколькими упоминаниями private-полей и вызовом конструктора из функции с теми параметрами, которые были переданы этой функции (т.е. метод apply для конструктора). |
Автор: АндрейМиндубаев 16.8.2008, 01:15 |
Се ля ви, если интересно, то я http://vingrad.ru/blogs/covex/2008/08/15/klassyi-v-js-vyizov-metodov-roditelskogo-klassa/ написал про свой вариант реализации наследования. ЗЫЖ Я читал этот форум в своём RSS-Reader-е, эта темка что-то остановилась - и я решил поделится своей разработкой. |
Автор: Се ля ви 19.8.2008, 13:49 | ||
АндрейМиндубаев, данная конкретная тема не столько остановилась, сколько довольно-таки естественным путём завершилась, перейдя в новое качество. Дело в том, что мне стала очевидна излишняя громоздкость своей модели наследования и все попытки упростить её носят по сути косметический характер. Так что результатом данного исследования я считаю выработку адекватной модели наследования с использованием всех возможностей инкапсуляции JavaScript, вот только для эффективного использования данной модели на практике нужна одна из трёх вещей: 1. Либо нужно радикальным образом её упростить. 2. Либо нужно создать инструментальные средства, позволяющие упростить разработку с учётом этой модели. 3. Либо нужно создать интерпретатор в JS из другого языка - в моём случае лучше всего подходит Java. В принципе, я сейчас работаю по всем трём направлениям. По первому пути я копаюсь в спеке и изучаю тонкости JS, постоянно думая о том, как их применить для упрощения данной модели. По второму - я, с одной стороны, разбираюсь с проектом http://developer.yahoo.com/yui/compressor/, который хочу приспособить для аналога компиляции JS. С другой стороны, продумываю новые элементы языка, которые можно было бы внести и на стадии такой компиляции превращать в более громоздкие конструкции, примеры которых вы можете видеть в этой теме. И с третьей стороны я ищу open-source редакторы, в которых можно было бы эту функциональность привнести наиболее эффективным образом - с подсветкой, подсказками и прочим. К сожалению, подающий надежды в этом плане http://www.spket.com/ не является open-source проектом... :( Ну и по третьему - уже, в общем-то, есть http://code.google.com/webtoolkit/. Однако, на мой взгляд, он слишком перегружен и я продумываю, как можно было бы легковесно реализовать многие вещи. Вот, например, со дня на день выложу сюда (отдельной темой) свою реализацию emum`ов как в Java - сейчас последние штрихи доделываю. В общем, процесс идёт. Главное - есть интерес к созданию удобного и лаконичного инструментария для создания RIA. А всё остальное - приложится ![]() |
Автор: Се ля ви 19.8.2008, 15:00 |
АндрейМиндубаев, насколько я понял, посмотрев ваш пример, вы не используете инкапсуляцию... |
Автор: АндрейМиндубаев 19.8.2008, 21:35 | ||||
Се ля ви, да - я не стал реализовывать инкапсуляцию. В языке, где всё публично, сокрытие чего-либо, скорее всего, бесполезно. Всё равно достанут, если припрёт:
(с) http://ajaxian.com/archives/evalfooa-objfn-how-you-arent-private-in-firefox Да и лишние действия на клиенте - это не есть гуд. Единственное, что мне было необходимо - это вызов функций "родительского класса". Правда, так получилось, что наследуются не классы, а сами функции (у любой "виртуальной функции" есть ссылка на "родительскую"). А мои "классы" - это всего лишь форма описания сущностей. Получилось упрощенно, необходимо_и_достаточно_для_меня.
|
Автор: Се ля ви 20.8.2008, 16:48 | ||||||
Дык, ведь,
![]() Конечно, если задаться целью - можно сломать механизм инкапсуляции - в Java с помощью RTTI, например. Просто это явно видно и является плохим тоном - по-этому так никто не делает. |
Автор: Се ля ви 20.1.2009, 16:04 |
Тут всё излагалось достаточно сумбурно, и остались некоторые ошибки. Сейчас привожу всё в порядок и раскладываю по полочкам, за одно улучшаю модель. Пишу статьями в своём блоге. Пока написал две статьи цикла "Особенности инкапсуляции на основе замыканий": 1. http://vingrad.ru/blogs/selavy/2009/01/17/javascript-osobennosti-inkapsulyatsii-na-osnove-zamyikaniy-chast-1/ 2. http://vingrad.ru/blogs/selavy/2009/01/20/javascript-osobennosti-inkapsulyatsii-na-osnove-zamyikaniy-chast-2-mehanizm-vyizova-metodov/. Кому сложно было разобраться со всем вышеизложенным материалом, но интересно - как на самом деле эффективно применять инкапсуляцию в prototype-based OOP-языках на примере JavaScript - милости прошу в мой блог. Буду писать этот цикл статей и дальше. |