📜 ⬆️ ⬇️

JavaScript patterns ... for dummies

One evening, right after I finished dealing with inheritance in JS, an idea occurred to me that it was time to do something more complicated - for example, patterns. A Gof book suddenly appeared on the table, and a work with the name “JavaScript patterns” appeared on the laptop screen.

In general, after a couple of evenings, I had descriptions and implementations on JavaScript of the most basic patterns - Decorator, Observer, Factory, Mediator, Memoization (not really a pattern, but rather a technique, but it seems to me that it fits perfectly in this row) and Singleton .


Decorator


Damn nice pattern, with its help you can change the object's behavior on the fly, depending on some conditions.
')
Suppose we have this code:

function Ball( param ) { this._radius = param.radius; this._color = param.color; } Ball.prototype = { constructor: Ball, INCREMENTATION_STEP: 5, draw: function(){console.log("ball drawn with radius:" + this._radius + " and color: " + this._color)}, inc: function(){ this._radius += this.INCREMENTATION_STEP } } new Ball({ radius:100, color:"red"}); 

Here we create a new red ball, and what if the ball needs not just red, but red striped? That's where the Decorator comes on stage.

Special charm gives him the fact that the original Ball does not suspect at all that it may be striped, or that it may have any decorators there.

You can implement the pattern in several ways:

Method one - integrated

 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(); } } 

In each decorator, you need to recreate all the functions that should be in the parent object, and in those of them whose behavior we do not want to change, you just need to redirect the request to the parent. This method is best used when major changes occur that affect> 1 - 2 functions.

We write a simple test:

 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(); 

Deep sigh and check:

 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 

In vain worried - everything works as it should.

The second way - lightweight

 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; } 

The code, of course, is needed less than in the first case, but if there are more than 1-2 changeable functions, or complex changes, it will be much more difficult to understand all this.

We write the test:

 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(); 

And check how it all works:

 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! 

All right.

Factory


Actually, the main task of a factory in statically typed languages ​​is to create different objects with the same interface, depending on the situation, in JavaScript this problem is not so urgent, so the question is - why is this factory needed here at all?

Everything is simple - in addition to this, the first goal, it also has a second one - the factory can carry out some initial initialization of objects.

For example, suppose we have Daddy, Mammy, and lad objects, creating them with the help of a factory, we can simply say - familyfactory.createLad (); familyfactory.createDaddy () , and the fact that they are both red and 210cm. growth, the factory will decide for us - we do not set these parameters.

Actually, in order for the factory to create some objects, it would be nice to ask the constructors first (in this example, the objects, unfortunately, are not as interesting as several lines above):

 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 "); } } 

And now you can make the factory itself - it can look like this:

 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 }); } } 

We write modest test:

 var factory = new ShapeFactory(100, "red") factory.makeSquare(); factory.makeSquare(); factory.makeTrinagle(); factory.makeCircle(); factory.makeTrinagle(); 

And look at the console:

 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 

Everything is working

Singleton


What is singleton? The explanation will be complex, long and nontrivial - this is an object that is in the system in one instance. Tadaam - the end of the explanation.

I often don’t even think about the fact that this is also a pattern. It is worth saying that before you apply it you should think carefully - in fact, you don't need a singleton very often.
It can be done in several ways.

The first method is trivial.

 var singleton_A = { log: function( text ){ console.log(text); } } 

This is a simple visual and effective method, which, in my opinion, does not even need an explanation.

The second way - show off

His main task is to show you how cool you are with your fellow students or other juniors. In addition, he, of course, can be really useful - with such an approach it is easier to restructure if plans changed and somewhere in the middle of the project they decided to replace the singleton with several objects

 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; }; })(); 

Its trick is that we simply create an object, but it’s a singleton or not - we don’t really care about us in general:

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

If we execute this code, we will see in the console:

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


Memoization


A very simple and useful technique - its essence is that for a function that can calculate the result for a long time, we create a small cache of answers. It works, of course, only in the case when, with the same input parameters, the result of the function must also be the same.

Create some slow function that uses this technique:

  function calculation(x, y) { var key = x.toString() + "|" + y.toString(); var result = 0; if (!calculation.memento[key]) { for (var i = 0; i < y; ++i) result += x; calculation.memento[key] = result; } return calculation.memento[key]; } calculation.memento = {}; 

And check how much time we can win:

  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(); 

If this code is now run in FF with Firebug, then we will see the following statistics:

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

As can be seen from the logs - when you re-request, we saved a lot of time.

Mediator


Mediator is such a thing that helps in especially neglected cases of interaction between objects, for example, when we have, say, 5 objects of more or less different types, and for some reason everyone knows about each other, you should seriously think about the mediator.

As a preparation, we first make several classes, which in future will use the mediator (a hint in this case, the mediator will be called 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; } } 

And now it's time to write the mediator itself:

 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(); } } 

And so, we have 4 objects working with the interaction between which could turn into a good punishment if it did not go through Mediator.

We write the verification code:

 var round_counter = 0; while (kitchen.daddy.getBeer()) { round_counter++ console.log( round_counter + " round passed"); } 

We ask the console - whether everything goes according to plan:

 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? 

I cut out some part of this soulful conversation, but in general, everything is as it should.

Observer


This is the same pattern that we use fifty times a day, even especially about it not thinking - $ (# some_useful_button) .click (blah_blah_blah) is a familiar construction? In it, the click is an event, and blah_blah_blah is the very Observer that is watching this event.

For your insidious plans, you can implement your own system of events and observers, who will be able to react not only to the actions of the user, but also to anything else.

Its key component is the event object:

 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]; } } 

Actually, I thought it was somehow boring without screenshots and links, so this time there will be two examples.

The first is simple


 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(); 

And the console confirms that everything works.

 wohoooooo yeaah! Top Secret:42 wohoooooo undefined Top Secret:42 

And the second ... is also simple, but prettier




You can touch it here.

For today, I think everything. All sources except Observer can be viewed here , and the Observer is a separate folder here.

Source: https://habr.com/ru/post/132472/


All Articles