Как стать автором
Обновить

Комментарии 30

Будет здорово, если в будущих статьях список реализаций пополнится. Спасибо.
Мне не хотелось загромождать описание паттернов большим количеством кода, особенно с учетом того, что среди нескольких реализаций как правило есть более приемлемая, чем все остальные. Но если на это есть спрос, то подумаю как лучше структурировать реализации так, чтобы не запутаться в них и прояснить плюсы минусы и подводные камни.
С описанием паттернов большинство на хабре уже худо-бедно знакомо, а вот различные варианты их реализации — уже более интересный материал. Чтобы не загромождать статью используйте спойлер.
Под описанием я имел ввиду именно основную на мой взгляд реализацию, как можете заметить собственно текстового описания на каждый паттерн приходится по несколько предложений. Но в ообщем я согласен, что чем больше реализаций, тем лучше.
Спасибо! Самое смешное, что первый линк даже есть в закладках, но был погребен под тоннами ссылок на бесполезне вещи.
На мой взгляд, многие классические паттерны в javascript просто не имеют смысл, так как этот язык гибче тех, на которые они в первую очередь рассчитаны.

Если бы стояла задача описать этот паттерн одной фразой, то она получилась бы примерно следующей: Singleton — это класс, который может иметь только один экземпляр.


Но в javascript нет классов! Проблема высосана из пальца.

Но у него есть и существенный недостаток: основная цель паттерна singleton — обеспечить доступ к объекту без использования глобальных переменных, а данный способ предоставляет доступ к переменной app только в текущей области видимости.


Facepalm. А если сделать функцию она магическим образом не будет глобальной? В requirejs можно возвращать и объект, вы не поверите.

Суть фабричного метода — не в том, что вы описали, а в том, чтобы предоставить возможность подклассам переопределить класс (или в общем случае — способ создания) объектов, которые создаются в процессе работы этого класса. В javascript такая задача тоже может появиться, но она решается просто добавление конструктора в объект и переопределением при необходимости:
var obj = { createAnimal: function(){ return new this.animal(); } }; obj.animal = Dog; obj.animal = Cat;

То что вы описали тоже решается без каких то проблем и мысле о «паттернах»:
var animal = { cat: Cat, dog: Dog }; new animal['cat'];
Хотел написать то же, вы меня опередили.
Т.к. в JS класс — такой же объект языка, как и переменная, то совершенно нет никакого смысла прятать обращение к переменной за вызов метода статического класса. Всё равно нужно держать где-то в области видимости некую переменную для доступа к синглтону, только лишний вызов функции добавится.
дополню:
обращение к синглтону подобным образом нелогично и противоречит здравому смыслу
var s = new Singleton();
Что делает оператор new? Создает новый объект. А тут всегда будет возвращатся один и тот же.
Еще код с использованием синглтонов сложнее покрывать тестами.
На мой взгляд, многие классические паттерны в javascript просто не имеют смысл, так как этот язык гибче тех, на которые они в первую очередь рассчитаны.

Дело Ваше, на мой взгляд имеют.

Но в javascript нет классов!

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

А если сделать функцию она магическим образом не будет глобальной? В requirejs можно возвращать и объект, вы не поверите.

Если использовать без какой-либо модульной системы, то да, конечно функция будет глобальной и профит по этой части получить не получится. Но я все-таки писал с оглядкой на requirejs (или другую модульную систему), в котором действительно можно возвращать объект, но это будет менее гибко: мы теряем возможность безболезненного избавления от дублирования кода путем наследования. Плюс такую реализацию, основанную на функциях довольно просто реализовать на CoffeeScript, выйдет практически тоже самое.

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

Цитата из книги Стефанова «JavaScript. Шаблоны»:
Назначение фабрики в том, чтобы создавать объекты. Этот шаблон обычно реализуется в виде классов или в виде статических методов классов и преследует следующие цели:
• Выполнение повторяющихся операций, необходимых при создании похожих объектов
• Предложить пользователям фабрики способ создания объектов без необходимости знать их тип (класс) на этапе компиляции


var obj = { createAnimal: function(){ return new this.animal(); } }; obj.animal = Dog; obj.animal = Cat;

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

То что вы описали тоже решается без каких то проблем и мысле о «паттернах»:
var animal = { cat: Cat, dog: Dog }; new animal['cat'];

Мыслей о паттернах может и не появляться, но этот код также является примитивной реализацией фабрики.
можно возвращать объект, но это будет менее гибко: мы теряем возможность безболезненного избавления от дублирования кода путем наследования.

Почему не гибко? Возвратить уже отнаследованный экземпляр чтоли нельзя?

var singleton = new Parent();
singleton.overloadedMethod = function(){};
return singleton;

Зачем создавать лишние сущности?
Отнаследованный можно, но что будете делать, если понадобится отнаследоваться от объекта singleton? Создавать обертку, которая будет содержать все методы объекта singleton, для переопределенных реализовывать новую логику, для сохранившихся вызывать метод сохраненного в обертке singleton? Это и называется менее гибко, какой объем работы надо будет проделать чтобы переименовать пару методов, а если наследников будет несколько? Можно еще конечно склонировать объект и переопределить нужные методы, но тут можно нарваться на грабли полного копирования объекта, т.к. поверхностное тут не подойдет, на которое можно потратить немало времени и между прочим по вычислительным ресурсам это тоже выйдет не самая легкая операция, вместо того чтобы ипользовать функции-констркуторы и прототипное наследование.
JavaScript очень гибкий, можно и от объекта наследоватся:

var child = Object.create(singleton);
в чисто прототипном ООП только так и делается. А джаваскриптовые функции-конструкторы созданы для совместимости с Java-оператором new.
Прототипное наследование вызывает те же проблемы, что и shallow copy. Даже больше — при клонировании хотя бы простые значения можно изменять в унаследованном объекте независимо.

Наследование объекта от объекта:

var parent = {},
    child;

function Surrogate(){}
Surrogate.prototype = parent;
child = new Surrogate();


или

var parent = {},
    child = Object.create(parent,{});


или

var parent = {},
    child = {}, 
    key;

for (key in parent){
    if (parent.hasOwnProperty(key) && !child.hasOwnProperty(key)){
        child[key] = parent[key];
    }
}


Наследование от синглтона сомнительно само по себе.
Раз уж вы ссылаетесь на Стефанова, приведу пару цитат из него:
Про синглтоны:
Usefulness warning: The following discussion is not so useful as a practical pattern but more as a theoretical exercise in imitating the workarounds for issues related to the designs of some (statically, strongly typed) class-based languages in which functions are not first-class
objects.

Про фабрики (цитату вы прервали на самом интересном месте)
The second point is more important in static class languages in which it may be nontrivial to create instances of classes, which are not known in advance (in compile time). In javaScript, this part of the implementation is quite easy.
Синглтон — дурацкий антипаттерн, убивающий читабельность и понимабельность кода чуть более, чем совсем.
Тот факт, что мы получаем ссылку не должне скрываться за декларацией создания нового объекта, это просто ложное утверждение, типа true = false # Happy debugging!.

Правильно использовать #clone() или фабрику объектов.
Чаще всего он применяется когда конечному пользователю класса не принципиально ссылка это или новый объект: объект для доступа к БД, объект текущего пользователя и пр. Память и вычисления экономятся, объект как-будто бы новый
Эмм… как это не важно? Объект он на то и объект, что обладает собственной «уникальностью» (ну, в грубом приближении). И когда мы создаем новый — он должен быть новым.
А то потом получаем всякое, что у Пети пол женским стал, потому что кто-то новую запись сделал, взяв как конструктор объект пользователя.
Текущий пользователь всегда один, и если описан класс именно для текущего пользователя, то создание объекта каждый раз при необходимости считать его данные, подтягивание данных из бд, и т.д. просто расточительно. В то же время при сохранении единого объекта и экономии ресурсов мы сохраняем страрый привычный интерфейс создания объекта. Повторю: речь именно о текущем пользователе, а Вы пытаетесь его применить к какому-то абстрактному пользователю.
Так яж про семантику.
user = new User # as singleton — плохо
user = Registry.get 'current_user' — хорошо

Использование синглтона как раз и отражает первую проблему в программировании — называние вещей.
Ну, что-то в этом есть, но опять же дело вкуса. Называете класс CurrentUserSingleton и проблем точно не будет
Называете класс CurrentUserSingleton и проблем точно не будет

Вы меня явно недооцениваете :)

А кроме шуток — вот уже не первый раз в итоге скатываюсь к тому, что DI + Registry наше все.
Тут нам и ре-юзабельность малой кровью, и возможность моки подпихивать и черт в ступе, если нужен.

Получается, к сожалению, такой если не god-object, конечно, но просветленный — это точно, но ничего не поделать, где-то в системе должны же зависимости хранится.
любимые слова: понимабельность и мониторирование, да?
НЛО прилетело и опубликовало эту надпись здесь
Прецеде́нт(случай)… Заебали!
НЛО прилетело и опубликовало эту надпись здесь
По моему такая реализация singleton по книге Стефанова более приемлемая:
var Universe; (function () { var instance; Universe = function Universe () { if (instance) { return instance; } instance = this; this.a = 10; } }()); Universe.prototype.b = 1;
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации