Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Организация кода в публичной библиотеке (https://javascript.ru/forum/misc/43311-organizaciya-koda-v-publichnojj-biblioteke.html)

Antonius 30.11.2013 23:30

Организация кода в публичной библиотеке
 
Здравствуйте.

В процессе обучения пишу скрипт (главная задача — именно учебная), который хоть и вряд-ли буду публиковать, но хотел бы, чтобы он был потенциально готов к публикации и распространению.

Схема использования — создается объект с данными, модифицируется с помощью собственных методов, затем считывается итоговое значение. Ничего уникального. Но в связи с особенностями задачи — применяться может очень интенсивно, от десятков тысяч до миллионов вызовов.

Возникла дилема — как организовать код, и с моей неопытностью я не могу принять взвешенное решение самостоятельно.

Вариант первый — классическое прототипное наследование в чистом виде. Создаю конструктор, присваиваю прототипу объект с методами. Либо каждый метод в отдельности — Constructor.prototype.method = function() { ... }, чтобы не затереть уже имеющиеся поля, вроде conctructor. «Приватные» методы обозначаются символом в имени, по сути оставаясь публичными.

Вариант второй. Функция, скрывающая приватные методы в замыкании, и возвращающая объект только с публичными методами.

Попытался составить тесткейс для сравнения производительности этих подходов, сделал два тестовых «класса» с абсолютно одинаковой функциональностю, реализованные по-разному.

По результатам моих измерений прототипное наслделование дает прирост в производительности, в зависимости от браузера, либо незначительный (4—5%), либо ощутимый (10—20% и больше).

К недостаткам прототипного наследования могу отнести видимость приватных методов извне («захламление») и необходимость использовать new с конструктором (или определить функцию-обертку для этого).

При создании замыканий получаем в некоторых условиях более медленный код, однако более «чистый» объект на выходе, без видимых приватных методов, и более простой в использовании (не требуется new или обертка).

Прошу помощи в выборе. Также смущает то, что сам не встречал публичных библиотек или фреймворков, использующих чистое прототипное наследование.

Спасибо.

Maxmaxmaximus3 01.12.2013 12:41

Цитата:

Сообщение от Antonius
К недостаткам прототипного наследования могу отнести видимость приватных методов извне

В чем недостаток? Рсскажи подробнее? Что мешает создать для них неймспейс префикс или постфикс например "_" ?

kobezzza 01.12.2013 13:03

Частая ошибка новичков изучающих ООП: модификаторы видимости private и protected созданы ИСКЛЮЧИТЕЛЬНО для увеличения уровня абстракции и банальной удобности, а не для "сокрытия от злых хакеров".

Я использую классическое соглашение:
_ private
__ protected

и всем советую

danik.js 01.12.2013 14:01

Помоему такого рода "приватные методы" (ввиде локальных функций) не оставляют возможности наследоваться от класса и перекрывать методы.

Более того, такой способ работает только для функций (ибо функции расшарены для всех экземпляров), а как же быть с приватными свойствами?

Кстати, никто не встречал использование вместо знака «_» знак «$» для приватных свойств/методов?

kobezzza 01.12.2013 14:06

Цитата:

Сообщение от danik.js (Сообщение 283765)
Помоему такого рода "приватные методы" (ввиде локальных функций) не оставляют возможности наследоваться от класса и перекрывать методы.

Ну приватные метода и не должны наследоваться, для этого есть протектед, хотя это конечно всё условности:) Прост по аналогии с Action Script 3 или C# и т.д.: модификатор "приватности" означает видимость в пределах класса, а модификатор "протектед" означает видимость также для его потомков.

Цитата:

Сообщение от danik.js (Сообщение 283765)
Кстати, никто не встречал использование вместо знака «_» знак «$» для приватных свойств/методов?

В ангуляре вроде так.

Maxmaxmaximus3 01.12.2013 14:55

Цитата:

Сообщение от danik.js
Помоему такого рода "приватные методы" (ввиде локальных функций) не оставляют возможности наследоваться от класса и перекрывать методы.

function Animal() {

  }

  Animal.prototype.say = function() {
    alert('i Animal')
  };



  Cat.prototype = Object.create(Animal.prototype); //наследуем
  function Cat() {

  }

  Cat.prototype.say = function() {
    this.constructor.prototype.say.call(this); //перекрытый метод
    alert('i Cat')
  };


  new Cat().say();



можно еще для простоты сделать так

Cat.prototype = Object.create(Animal.prototype); //наследуем
  Cat.prototype.parent = Cat.prototype;
  function Cat() {

  }

  Cat.prototype.say = function() {
    this.parent.say.call(this); //перекрытый метод
    alert('i Cat')
  };



Цитата:

Сообщение от kobezzza
В ангуляре вроде так.

кстати раз уж я пытаюсь копировать их апи и названия методов, я на всякий скопировал и эту традицию, теперь бесит =)

Antonius 02.12.2013 01:51

Цитата:

Сообщение от Maxmaxmaximus3 (Сообщение 283756)
В чем недостаток? Рсскажи подробнее? Что мешает создать для них неймспейс префикс или постфикс например "_" ?

Разумеется, если остановлюсь на этом варианте, то так и будет сделано.

Цитата:

Сообщение от kobezzza (Сообщение 283758)
Частая ошибка новичков изучающих ООП: модификаторы видимости private и protected созданы ИСКЛЮЧИТЕЛЬНО для увеличения уровня абстракции и банальной удобности, а не для "сокрытия от злых хакеров".

Я использую классическое соглашение:
_ private
__ protected

и всем советую

Это очевидно же. Дело в другом. Если есть причины сделать метод приватным, то это же не просто так. Например, у него может в будущем поменяться набор или формат параметров или тип/формат возвращаемого значения, он может быть переименован или удален. Да и просто он может выполнять сугубо служебные функции, и «не надо хотеть» использовать его извне. Публично доступен стабильный (хочется надеяться) API,

Но для выбора в моем случае это всего лишь один из «недостатков». Также смущает необходимость использования new. В подобного рода скриптах, рассчитанных на массовое использование, мне подобное требование не встречалась (неопытность? такие библиотеки, скрипты существуют?)

Цитата:

Сообщение от danik.js (Сообщение 283765)
Помоему такого рода "приватные методы" (ввиде локальных функций) не оставляют возможности наследоваться от класса и перекрывать методы.

Более того, такой способ работает только для функций (ибо функции расшарены для всех экземпляров), а как же быть с приватными свойствами?

Кстати, никто не встречал использование вместо знака «_» знак «$» для приватных свойств/методов?

Наследоваться и перекрывать методы (публичные, разумеется), можно при использовании «паразитного», «фабричного» наследования, разве нет? А менять служебные приватные методы как минимум опасно — на их основе работает остальной код, для чего может потребоваться их изменение (а не доопределение новых)? Ко всем данным (приватным свойствам), которые имеют смысл за пределами объекта, должен быть обеспечен доступ средствами открытого API, не так ли? Что заодно позволит выполнять валидацию и коррекцию ошибочных данных (если это возможно) при доступе к ним. В моем случае геттеры-сеттеры я применять не планирую — поддержка у них для этой задачи неудовлетворительная пока что.

Да, и еще вопрос вдогонку. Те, кто использует классическое прототипное наследование, в каком формате вы присваиваете значение прототипу? Constructor.prototype = { … } или Constructor.prototype.field = …?

Maxmaxmaximus3 02.12.2013 02:10

Цитата:

Сообщение от Antonius
Это очевидно же. Дело в другом. Если есть причины сделать метод приватным, то это же не просто так. Например, у него может в будущем поменяться набор или формат параметров или тип/формат возвращаемого значения, он может быть переименован или удален. Да и просто он может выполнять сугубо служебные функции, и «не надо хотеть» использовать его извне.

ииииииииииии????

Цитата:

Сообщение от Antonius
Но для выбора в моем случае это всего лишь один из «недостатков».

В чем недостаток то?

Цитата:

Сообщение от Antonius
для чего может потребоваться их изменение

Очевидно что когда говорится не поддерживается переопределение имеется ввиду нет удобного доступа к оригиналу из перекрытого. а зачем нужен доступ к оригиналу? потому что когда метод перекрывается, старый функционал сохраняется а новый нарасчивается. это к слову

Цитата:

Сообщение от Antonius
Constructor.prototype.field

Потому что лучше сжимается мигификаторами =) при такой записи они могут вообще заинлайнить код функции то есть если его мало, то взять и подставить его в те места где метод вызывается.

nerv_ 02.12.2013 02:21

Прототипы + соглашения + обертка для new. С другой стороны смотря для кого пишешь - можно и не оборачивать new. Тот, кто используют приватные методы извне - сам дурак :)

Antonius 02.12.2013 02:30

Цитата:

Сообщение от Maxmaxmaximus3 (Сообщение 283901)
ииииииииииии????

И если не надо хотеть его использовать извне — не надо давать возможность его использовать извне. Но дальше я уже понял, какие детали упускаю из виду.

Цитата:

Сообщение от Maxmaxmaximus3 (Сообщение 283901)
В чем недостаток то?

Потому и в кавычках. Не недостаток, просто некрасиво что-ли...

Цитата:

Сообщение от Maxmaxmaximus3 (Сообщение 283901)
Очевидно что когда говорится не поддерживается переопределение имеется ввиду нет удобного доступа к оригиналу из перекрытого. а зачем нужен доступ к оригиналу? потому что когда метод перекрывается, старый функционал сохраняется а новый нарасчивается. это к слову

Да, согласен.

Цитата:

Сообщение от Maxmaxmaximus3 (Сообщение 283901)
Потому что лучше сжимается мигификаторами =) при такой записи они могут вообще заинлайнить код функции то есть если его мало, то взять и подставить его в те места где метод вызывается.

Убедительно. И существующее не трогаем, и при минификации выигрываем. Спасибо.

Antonius 02.12.2013 02:34

Цитата:

Сообщение от nerv_ (Сообщение 283904)
Прототипы + соглашения + обертка для new. С другой стороны смотря для кого пишешь - можно и не оборачивать new. Тот, кто используют приватные методы извне - сам дурак :)

Ну если бы только сам использовал — не парился бы насчет new, а если все-таки опубликую — надо будет или обертку самому добавить, или в инструкции описать. Сам добавлять почему не хочу — во-первых это лишний элемент в пространстве имен, помимо самого конструктора, а во-вторых (и это главное) не могу придумать имя, которое бы было коротким, понятным, и при этом не было похоже на типичное имя переменной, которая уже может быть в проекте. Поэтому пусть сами имя назначают :)

nerv_ 02.12.2013 02:35

Цитата:

Сообщение от Antonius
во-первых это лишний элемент в пространстве имен, помимо самого конструктора, а во-вторых (и это главное) не могу придумать имя, которое бы было коротким, понятным, и при этом не было похоже на типичное имя переменной

var obj = Constructor.create(params);

Constructor.create = function(params) {
    return new Constructor(params);
};

kobezzza 02.12.2013 02:36

Цитата:

Сообщение от nerv_ (Сообщение 283904)
Тот, кто используют приватные методы извне - сам дурак :)

золотые слова:)

К слову, если очень нужно запретить изменение метода, то можно использовать "заморозку" (seal, freeze, preventExtensions), но не понимаю зачем, ибо если челочек осмысленно юзает приватные методы извне, то он априори не прав.

Antonius 02.12.2013 02:50

Насчет того, как принято в JS, кажется начинаю понимать :)
В принципе некоторый опыт с другими языками (в том числе ООП) был, но давно. И по сравнению с ними в JS все как-то непривычно, кажется, надо просто понять, что «тут это нормально».

Кстати, где можно почитать насчет seal, freeze, preventExtensions и т. д. Использовать здесь точно не буду, просто чтобы быть в курсе...


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

Если бы объекты создавались редко, а затем интенсивно модифицировались — можно было бы и так, но пока мне кажется, что именно для этой задачи — не очень красивый вариант.

nerv_ 02.12.2013 11:56

Цитата:

Сообщение от Antonius
Кстати, где можно почитать насчет seal, freeze, preventExtensions и т. д. Использовать здесь точно не буду, просто чтобы быть в курсе...

http://learn.javascript.ru/descriptors-getters-setters

Цитата:

Сообщение от Antonius
Если бы объекты создавались редко, а затем интенсивно модифицировались — можно было бы и так, но пока мне кажется, что именно для этой задачи — не очень красивый вариант.

можешь использовать "Шаблоны принудительного использования new":
1.
'use strict';

function Waffle() {
    alert(this === undefined);
    this.tastes = 'yummy';
}

new Waffle();
Waffle();

В строгом режиме ES5 ссылка this больше не указывает на глобальный объ-
ект.
2.Возвращать из конструктора "другой" объект:
function Waffle() {
    var obj = Object.create(Waffle.prototype);
    obj.tastes = 'yummy';
    alert(obj instanceof Waffle);
    return obj; 
}
new Waffle();
Waffle();

3.Использовать конструктор вызывающий сам себя:
function Waffle() {
    if (!(this instanceof Waffle)) {
        return new Waffle();
    }
    this.tastes = 'yummy';
    alert(this instanceof Waffle);
}
new Waffle();
Waffle();


*писал на скорую руку, мог ошибиться...

Maxmaxmaximus3 02.12.2013 13:22

Цитата:

Сообщение от nerv_
В строгом режиме ES5 ссылка this больше не указывает на глобальный объ-
ект.

в смысле при вызове любой функции всегда создается Object.create(func.prototype) ????

kobezzza 02.12.2013 14:49

Цитата:

Сообщение от Maxmaxmaximus3 (Сообщение 283966)
в смысле при вызове любой функции всегда создается Object.create(func.prototype) ????

Если у функции не указан объект вызова, то он равен undefined (вместо window как раньше было)

Antonius 02.12.2013 17:08

nerv_, спасибо :) Первый способ не подходит, поскольку требует обязательного использования "use strict", но на будущее буду иметь в виду. Остальные — интересно.

Все-таки учиться только самостоятельно по источникам в учебниках, литературе не так эффективно, вот даже по простым вопросам общение с более опытными разработчиками сразу приносит ощутимую пользу.

Antonius 09.12.2013 06:53

Пожалуй, придется поднять тему снова :(

Возник такой вопрос (опять же из разряда «как принято поступать в таких случаях»). Опять же не знаю, пригодится оно мне в итоге или нет, но разобраться в любом случае хочется.

В корневом объекте, который объявлен в скрипте, есть поля, в свою очередь хранящие структурированную информацию. Например, координаты
{ x: 1, y: 2}
. Сейчас, чтобы проверить, что там действительно находятся координаты, иногда приходится использовать проверки типа
if (this.coord && typeof this.coord.x == 'number' && typeof this.coord.y == 'number') {…}

Например, если данные получены извне и еще не провалидированы.

Есть мысль объявить для таких объектов конструкторы и проверять
if (this.coord instanceof Coord) {…}

Валидировать можно при инициализации объекта, например.

Опять возникает проблема засорения пространства имен. Библиотека уже объявляет корневой объект (например, LibraryRoot, сам является конструктором), и не хочется помимо него вводить отдельные конструкторы.

Пока приходит в голову только объявлять конструкторы как LibraryRoot.Coord, но что-то меня смущает в этом варианте. Собственно, вопрос, нормально ли это или есть способы получше?

kobezzza 09.12.2013 08:16

LibraryRoot.Coord. Нормально, много так где сделано.

Antonius 09.12.2013 15:20

Спасибо :)


Часовой пояс GMT +3, время: 10:36.