Singleton pattern in javascript

JavaScript паттерны… для чайников

Однажды вечером, сразу после того, как я закончил разбираться с наследованием в JS, мне пришла в голову идея, что пора бы заняться чем-нибудь посложнее — например паттернами. На столе внезапно оказалась книжка Gof, а на экране ноутбука появился труд с названием «JavaScript patterns».

В общем, спустя пару вечеров, у меня появились описания и реализации на JavaScriptе самых основных паттернов — Decorator, Observer, Factory, Mediator, Memoization (не совсем паттерн, а скорее техника, но мне кажется что она прекрасно в этот ряд вписывается) и Singleton.

Decorator

Чертовски приятный паттерн, с его помощью можно менять поведение объекта на лету, в зависимости от каких-нибудь условий.

Допустим, у нас есть такой код:

function Ball( param ) < this._radius = param.radius; this._color = param.color; >Ball.prototype = < constructor: Ball, INCREMENTATION_STEP: 5, draw: function(), inc: function() < this._radius += this.INCREMENTATION_STEP >> new Ball(< radius:100, color:"red">); 

Здесь мы создаем новый красный мячик, а что делать если мячик нужен не просто красный, а красный в полоску? Вот тут на сцену и выходит Decorator.

Особый шарм ему придает то, что первоначальный Ball вообще не подозревает о том, что он может быть в полоску, или что у него могут быть какие-то там декораторы.

Реализовать паттерн можно несколькими способами:

Способ первый — комплексный
function StripedBall( ball ) < this._ball = ball >StripedBall.prototype = < constructor: StripedBall, draw: function() < this._ball.draw(); console.log("and with stripes"); >, inc: function() < return this._ball.inc(); >> function SpeckledBall( ball ) < this._ball = ball >SpeckledBall.prototype = < constructor: SpeckledBall, draw: function() < this._ball.draw(); console.log("and with dots!"); >, inc: function() < return this._ball.inc(); >> 

В каждом декораторе нужно воссоздать все функции которые должны быть в объекте родителе, и в тех из них, поведение которых мы менять не хотим, нужно просто перенаправлять запрос родителю. Этот способ лучше применять когда происходят серьезные изменения, которые затрагивают > 1 — 2 функций

var ball1 = new SpeckledBall( new StripedBall( new Ball(< radius:100, color:"red">))); var ball2 = new StripedBall( new SpeckledBall( new Ball(< radius:100, color:"green">))); ball1.draw(); ball1.inc(); ball1.draw(); ball2.draw(); 

Глубокий вздох, и проверка:

ball drawn with radius:100 and color: red and with stripes and with dots! ball drawn with radius:105 and color: red and with stripes and with dots! ball drawn with radius:100 and color: green and with dots! and with stripes 

Зря волновался — работает все как надо.

Способ второй — легковесный
function MakeStripedBall( ball ) < var function_name = "draw"; var prev_func = ball[ function_name ]; ball[ function_name ] = function() < prev_func.apply( this, arguments ) console.log("and with stripes"); >; return ball; > function MakeSpeckledBall( ball ) < var function_name = "draw"; var prev_func = ball[function_name]; ball[function_name] = function () < prev_func.apply(this, arguments) console.log("and with dots!"); >; return ball; > 

Кода, конечно, нужно меньше чем в первом случае, зато, если изменяемых функций больше чем 1-2, или изменения комплексные — разобраться во всем этом будет намного сложнее.

var ball3 = MakeStripedBall( MakeSpeckledBall( new Ball(< radius: 150, color: "blue" >))); var ball4 = MakeSpeckledBall( MakeStripedBall(new Ball(< radius: 150, color: "blue" >))); ball3.draw(); ball3.inc(); ball3.draw(); ball4.draw(); 

И проверяем, как все это работает:

ball drawn with radius:150 and color: blue and with dots! and with stripes ball drawn with radius:155 and color: blue and with dots! and with stripes ball drawn with radius:150 and color: blue and with stripes and with dots! 

Factory

Собственно, основной задачей фабрики в статически типизируемых языках является создание разных объектов с одинаковым интерфейсом, в зависимости от ситуаций, в JavaScript этак проблема так остро не стоит, так что появляется вопрос — зачем эта фабрика тут вообще нужна?

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

Например, предположим, у нас есть объекты Daddy, Mammy, и lad, создавая их с помощью фабрики мы можем просто сказать — familyfactory.createLad(); familyfactory.createDaddy(), а уж то, что они оба рыжие и 210см. роста, за нас решит фабрика — эти параметры мы не задаем.

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

var Shapes = < Circle: function (param) < console.log("new " + param.color + " circle created with radius " + param.radius + "px"); >, Square: function (param) < console.log("new " + param.color + " square created with " + param.side + "px on a side "); >, Triangle: function (param) < console.log("new " + param.color + " triangle created with " + param.side + "px on a side "); >> 

А теперь можно сделать и саму фабрику — выглядеть она может так:

function ShapeFactory(size, color) < this.size = size; this.color = color; >ShapeFactory.prototype = < constructor: ShapeFactory, makeCircle: function () < return new Shapes.Circle(< radius: this.size / 2, color: this.color >); >, makeSquare: function () < return new Shapes.Square(< side: this.size, color: this.color >); >, makeTrinagle: function () < return new Shapes.Triangle(< side: this.size, color: this.color >); > > 
var factory = new ShapeFactory(100, "red") factory.makeSquare(); factory.makeSquare(); factory.makeTrinagle(); factory.makeCircle(); factory.makeTrinagle(); 
new red square created with 100px on a side new red square created with 100px on a side new red triangle created with 100px on a side new red circle created with radius 50px new red triangle created with 100px on a side 

Singleton

Что же такое синглтон? Объяснение будет сложным, долгим и нетривиальным — это объект, который есть в системе в одном экземпляре. Тадаам — конец объяснения.

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

Способ первый — тривиальный

Это простой наглядный и эффективный метод, который, даже, в объяснении, по-моему, не нуждается.

Способ второй — выпендрежный

Основная его задача — это показать какой ты крутой однокурсникам или другим джуниорам. Кроме этого он, конечно, может быть действительно полезен — с таким подходом проще перестраиваться если планы изменились и где-то в середине проекта синглетон решили заменить несколькими объектами

var Singleton_B; (function() < var instance; var anticlone_proxy; Singleton_B = function()< if( instance )< return instance; >instance = < _counter: 0, log: function( text )< this._counter++; console.log( text + this._counter ); >> anticlone_proxy = < log: function( text )< return instance.log( text ); >> return anticlone_proxy; >; >)(); 

Его фишка в том что мы просто создаем объект, а синглетон он, или нет — нас в общем-то не очень волнует:

 function NonSingleton() < >NonSingleton.prototype = < consturctor: NonSingleton, scream: function()> var singleton = new Singleton_B(); var nonsingleton = new NonSingleton(); singleton.log("3..2..1. ignition!"); nonsingleton.scream(); 

Если этот код выполнить, то в консоли мы увидим:

3..2..1. ignition! Woooohoooooo! 

Memoization

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

Создаем какую-нибудь медленную функцию, которая использует эту технику:

 function calculation(x, y) < var key = x.toString() + "|" + y.toString(); var result = 0; if (!calculation.mementoSingleton pattern in javascript) < for (var i = 0; i < y; ++i) result += x; calculation.mementoSingleton pattern in javascript = result; >return calculation.mementoSingleton pattern in javascript; > calculation.memento = <>; 

И проверяем сколько мы можем выйграть времени:

 console.profile(); console.log('result:' + calculation(2, 100000000)); console.profileEnd(); console.profile(); console.log('result:' + calculation(2, 100000000)); console.profileEnd(); console.profile(); console.log('result:' + calculation(2, 10000000)); console.profileEnd(); 

Если этот код теперь запустить в FF с Firebug, то мы увидим следующую статистику:

Profile1: 626.739ms result:200000000 0.012ms result:200000000 63.055msresult:20000000 

Как видно из логов — при повторном запросе мы сэкономили кучу времени.

Mediator

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

В качестве подготовки сначала сделаем несколько классов, которые в перспективе медиатор будут использовать (подсказка в данном случае медиатор будет называтья kitchen:)

function Daddy() < >Daddy.prototype = < constructor: Daddy, getBeer: function () < if (!kitchen.tryToGetBeer()) < console.log("Daddy: Who the hell drank all my beer?"); return false; >console.log("Daddy: Yeeah! My beer!"); kitchen.oneBeerHasGone(); return true; >, argue_back: function () < console.log("Daddy: it's my last beer, for shure!"); >> function Mammy() < >Mammy.prototype = < constructor: Mammy, argue: function () < console.log("Mammy: You are f*king alconaut!"); kitchen.disputeStarted(); >> function BeerStorage(beer_bottle_count) < this._beer_bottle_count = beer_bottle_count; >BeerStorage.prototype = < constructor: BeerStorage, takeOneBeerAway: function () < if (this._beer_bottle_count == 0) return false; this._beer_bottle_count--; return true; >> 

А теперь пора написать и сам медиатор:

var kitchen = < daddy: new Daddy(), mammy: new Mammy(), refrigerator: new BeerStorage(3), stash: new BeerStorage(2), tryToGetBeer: function () < if (this.refrigerator.takeOneBeerAway()) return true; if (this.stash.takeOneBeerAway()) return true; return false >, oneBeerHasGone: function ()< this.mammy.argue(); >, disputeStarted: function () < this.daddy.argue_back(); >> 

И так, у нас есть 4 объекта работа со взаимодействием между которыми, могла бы превратиться в неплохое наказание, если бы проходила не через Mediator.

var round_counter = 0; while (kitchen.daddy.getBeer())

Спрашиваем у консоли — все ли идет по плану:

Daddy: Yeeah! My beer! Mammy: You are f*king alconaut! Daddy: it's my last beer, for shure! 1 round passed . Daddy: Yeeah! My beer! Mammy: You are f*king alconaut! Daddy: it's my last beer, for shure! 5 round passed Daddy: Who the hell drank all my beer? 

Некоторую часть этой душевной беседы я вырезал, но в целом все как надо.

Observer

Это тот самый паттерн, который мы используем по пятьдесят раз в день даже особенно об этом незадумывасяь — $(#some_useful_button).click( blah_blah_blah ) — знакомая конструкция? В ней click — это событие, а blah_blah_blah какраз и есть тот самый Observer который за этим событием наблюдает.

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

Ключевым её компонентом является объект событие:

Event = function() < this._observers = []; >Event.prototype = < raise: function (data) < for (var i in this._observers) < var item = this._observers[i]; item.observer.call(item.context, data); >>, subscribe: function (observer, context) < var ctx = context || null; this._observers.push(< observer: observer, context: ctx >); >, unsubscribe: function (observer, context ) < for (var i in this._observers) if ( this._observers[i].observer == observer && this._observers[i].context == context ) delete this._observers[i]; >> 

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

Первый — простой
var someEvent = new Event(); someEvent.subscribe(function ( data ) < console.log("wohoooooo " + data ) >); var someObject = < _topSecretInfo: 42, observerFunction: function () < console.log("Top Secret:" + this._topSecretInfo) >> someEvent.subscribe(someObject.observerFunction, someObject); someEvent.raise("yeaah!"); someEvent.raise(); 

И консоль подтверждает что все работает.

wohoooooo yeaah! Top Secret:42 wohoooooo undefined Top Secret:42 
И второй… тоже простой, но посимпатичнее

На сегодня, я думаю всё. Все исходники кроме Observer можно посмотреть вот здесь, а Observer лежит отдельной папкой тут

Источник

Читайте также:  Templates in cpp file
Оцените статью