📜 ⬆️ ⬇️

Using design patterns in javaScript: Spawning patterns

Hi, Habr!
I was surprised to find the absence of a detailed article on the subject on Habré, which immediately led me to correct this blatant injustice.

In conditions when the client part of web applications is becoming increasingly thick, business logic inexorably creeps onto the client, and the sovereignty of server technologies more and more easily encroaches on node.js one cannot but reflect on the design techniques of javaScript architecture. And in this case, we certainly should be helped by design patterns — the template methods for solving common problems. Patterns help build an architecture that requires you to make the least effort to make changes. But you should not take them as a panacea, that is, roughly speaking, if the quality of the code is not a fountain, it teems with hardcode and a hard link between logically independent modules, then no patterns will save it. But if the task is to design a scalable architecture, the patterns can be a good help.
But however, this article is not about the design patterns as such, but about their use in javaScript. In the first part of this article, I will write about the application of generating patterns.


Singleton


If the task was to describe this pattern in one phrase, it would have turned out to be approximately the following: Singleton is a class that can have only one instance.
The simplest and most obvious solution in javaScript for implementing this pattern is to use objects:

var app = { property1: 'value', property2: 'value', ... method1: function () { ... }, ... } 

')
This method has its advantages and disadvantages. It is easy to describe, many use it without knowing the existence of any patterns, and this form of writing will be understandable to any javaScript developer. But it also has a significant drawback: the main purpose of the singleton pattern is to provide access to the object without using global variables, and this method provides access to the app variable only in the current scope. This means that we can access the app object from anywhere in the application only if it is global. Most often this is extremely unacceptable, a good style of development in javaScript is to use a maximum of one global variable in which everything necessary is encapsulated. This means that we can use the above approach a maximum of once per application.
The second method is a bit more complicated, but it is more versatile:

 function SomeFunction () { if (typeof (SomeFunction.instance) == 'object') { return SomeFunction.instance; } this.property1 = 'value'; this.property2 = 'value'; SomeFunction.instance = this; return this; } SomeFunction.prototype.method1 = function () { } 


Now, using any modular system (for example, requirejs), we can connect a file with the description of this constructor function anywhere in our application and access our object by running:

 var someObj = new SomeFunction (); 


But this method also has its drawback: the instance is stored simply as a static property of the constructor, which allows anyone to overwrite it. We want so that under any circumstances we can get access from any corner of our application to the required object. This means that the variable in which we save the instance should be made private, and the closure will help us in this.

 function SomeFunction () { var instance; SomeFunction = function () { return instance; } this.property1 = 'value'; this.property2 = 'value'; instance = this; } 


It would seem that this is the solution to all problems, but new ones are taking the place of old problems. Namely: all the properties listed in the prototype of the designer after the creation of the instance will not be available, because in essence, they will be written into the old constructor, and not into a freshly defined one. But there is a decent way out of this situation:

 function SomeFunction () { var instance; SomeFunction = function () { return instance; } SomeFunction.prototype = this; instance = new SomeFunction (); instance.constructor = SomeFunction; instance.property1 = 'value'; instance.property2 = 'value'; return instance; } 


This way of describing loners is devoid of all the above disadvantages and is quite suitable for universal use, however, the methods of describing loners using closures will not work with requirejs, but if you modify them a little and take the variable out of the closure created by the function itself to the function used in define, then the problem will be solved:

 define([], function () { var instance = null; function SomeFunction() { if (instance) { return instance; } this.property1 = 'value'; this.property2 = 'value'; instance = this; }; return SomeFunction; }); 


Factory method


The factory method has two main goals:
1) Do not use explicitly specific classes.
2) Merge commonly used object initialization methods together.
The simplest implementation of the factory method is an example:

 function Foo () { //... } function Bar () { //... } function factory (type) { switch (type) { case 'foo': return new Foo(); case 'bar': return new Bar(); } } 


Accordingly, the creation of objects will look like this:

 foo = factory('foo'); bar = factory('bar'); 


You can use a more elegant solution:

 function PetFactory() { }; PetFactory.register = function(name, PetConstructor) { if (name instanceof Function) { PetConstructor = name; name = null; } if (!(PetConstructor instanceof Function)) { throw { name: 'Error', message: 'PetConstructor is not function' } } this[name || PetConstructor.name] = PetConstructor; }; PetFactory.create = function(petName) { var PetConstructor = this[petName]; if (!(PetConstructor instanceof Function)) { throw { name: 'Error', message: 'constructor "' + petName + '" undefined' } } return new PetConstructor(); }; 


In this case, we do not limit ourselves to the number of classes that a factory can generate; we can add as many as you like this way:

 PetFactory.register('dog', function() { this.say = function () { console.log('gav'); } }); 


Well, or so:

 function Cat() { } Cat.prototype.say = function () { console.log('meow'); } PetFactory.register(Cat); 


Abstract Factory


An abstract factory is used to create a group of interrelated or interdependent objects.
Suppose we have several pop-up windows, which consist of identical elements, but these elements look differently and react differently to user actions. Each of these elements will be created by the factory method, which means that each type of pop-up windows needs its own factory of objects.
For example, let's describe the BluePopupFactory factory, it has the exact same structure as PetFactory, so we’ll skip the details and just use it.

 function BluePopup () { //   } BluePopup.prototype.attach = function (elemens) { //  ui-   } BluePopupFactory.register('popup', BluePopup); function BluePopupButton () { //      } BluePopupButton.prototype.setText = function (text) { //    } BluePopupFactory.register('button', BluePopupButton); function BluePopupTitle () { //     } BluePopupTitle.prototype.setText = function (text) { //   } BluePopupFactory.register('title', BluePopupTitle); 


Probably we should have a certain class responsible for the interface elements.

 function UI () { //,   ui- } 


And we will add the createPopup method to it:

 UI.createPopup = function (factory) { var popup = factory.create('popup'), buttonOk = factory.create('button'), buttonCancel = factory.create('button'), title = factory.create('title'); buttonOk.setText('OK'); buttonCancel.setText('Cancel'); title.setText('Untitled'); popup.attach([buttonOk, buttonCancel, title]); } 


As you can see, createPopup takes a factory argument, creates a popup window and buttons with a title for it, and then attaches them to the window.
After that you can use this method like this:

 var newPopup = UI.createPopup(BluePopupFactory); 


Accordingly, it is possible to describe an unlimited number of factories and transfer the necessary one when creating the next pop-up window.

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


All Articles