MASTERING JAVASCRIPT DESIGN PATTERNS Create Scalable and Reliable Applications With Advanced Javascript Design Patterns Using... (Tomas Corral Cosas)
MASTERING JAVASCRIPT DESIGN PATTERNS Create Scalable and Reliable Applications With Advanced Javascript Design Patterns Using... (Tomas Corral Cosas)
ways code can be structured. Again we’ll pay attention to the classic
design patterns as described in the GoF book. Structural Patterns
using ES2015/2017/2018.
Chapter 11, Advanced Patterns, this chapter will cover some advanced
patterns in JavaScript. Most of them are implemented as wild
language hacks.
Chapter 17, ES2019 What is ESNEXT, in this chapter, we'll look at what
are the features that will be introduced in ES2019 a.k.an ESNext.
Table of Contents
Preface
Introduction
A pause
Javascript everywhere
Anti-patterns
Summary
Questions
Further Reading
Namespaces
ession
Modular programming
UMD
Module types
AMD
CommonJS
require
module.exports
exports.<keyName>
class
interface
extends
implements
PROTOTYPE EXTENSIONS
OOP in ES6
Summary
Questions
Further reading
3. Functional Programming
Introduction
Technical Requirements
ing
Higher-order functions
MapReduce pattern
Map
Reduce
Filter
Immutable.js
Memoization
Implementation
Lazy instantiation
Implementation
Composing
Functional libraries
React.js
Redux.js
Rxjs
Lodash
Ramda.js
Bacon.js
Summary
Questions
Further Reading
4. Reactive Programming
Introduction
Technical Requirements
Streams
Filtering streams
Merging streams
Summary
Questions
Further Reading
5. Creational Patterns
Introduction
Technical Requirements
Singleton
Example
Pros
Cons
Alternative
Prototype
Example
Pros
Cons
Constructor
Example
Factory method
Example
Pros
Cons
Abstract Factory
Example
Pros
Cons
Builder
tern is
Example
Pros
Cons
Summary
Questions
Further Reading
6. Structural Patterns
Introduction
Technical Requirements
Adapter
Example
Pros
Cons
Composite
Example
Pros
Cons
Decorator
Example
Pros
Cons
Bridge
Example
Pros
Cons
Facade
Example
Pros
Cons
Flyweight
Example
Pros
Cons
Proxy
Example
Pros
Cons
Summary
Questions
Further Reading
7. Behavioural Patterns
8. Performance patterns
9. Asynchronous patterns
15. Micro-services
16. ES2015/2017/2018 Solutions Today and the Road ahead
In the first half of this chapter, we'll explore the history of JavaScript
and how it came to be the important language that it is today. As
JavaScript has evolved and grown in importance, the need to apply
rigorous methods to its construction has also grown. Design patterns
can be a very useful tool to assist in developing maintainable code.
The second half of the chapter will be dedicated to the theory of
design patterns. Finally, we'll look briefly at anti-patterns.
The road to JavaScript
We'll never know how language first came into being. Did it slowly
evolve from a series of grunts and guttural sounds made during
grooming rituals? Perhaps it developed to allow mothers and their
offspring to communicate. Both of these are theories, all but
impossible to prove. Nobody was around to observe our ancestors
during that important period. In fact, the general lack of empirical
evidence led the Linguistic Society of Paris to ban further discussions
on the topic, seeing it as unsuitable for serious study.
The early days
Fortunately, programming languages have developed in recent history
and we've been able to watch them grow and change. JavaScript has
one of the more interesting histories of modern programming
languages. During what must have been an absolutely frantic 10 days
in May of 1995, a programmer at Netscape wrote the foundation for
what would grow up to be modern JavaScript.
At the time, Netscape was involved in the first of the browser wars
with Microsoft. The vision for Netscape was far grander than simply
developing a browser. They wanted to create an entire distributed
operating system making use of Sun Microsystems' recently-released
Java programming language. Java was a much more modern
alternative to the C++ Microsoft was pushing. However, Netscape
didn't have an answer to Visual Basic. Visual Basic was an easier to
use programming language, which was targeted at developers with
less experience. It avoided some of the difficulties around memory
management that make C and C++ notoriously difficult to program.
Visual Basic also avoided strict typing and overall allowed more
leeway.
The name has caused much confusion over the years. JavaScript is a
very different language from Java. JavaScript is an interpreted
language with loose typing, which runs primarily on the browser.
Java is a language that is compiled to bytecode, which is then
executed on the Java Virtual Machine. It has applicability in
numerous scenarios, from the browser (through the use of Java
applets) to the server (Tomcat, JBoss, and so on), to full desktop
applications (Eclipse, OpenOffice, and so on). In most laypersons'
minds, the confusion remains.
JavaScript turned out to be really quite useful for interacting with the
web browser. It was not long until Microsoft had also adopted
JavaScript into their Internet Explorer to complement VBScript. The
Microsoft implementation was known as JScript.
By late 1996, it was clear that JavaScript was going to be the winning
web language for the near future. In order to limit the amount of
language deviation between implementations, Sun and Netscape
began working with the European Computer Manufacturers
Association (ECMA) to develop a standard to which future versions
of JavaScript would need to comply. The standard was released very
quickly (very quickly in terms of how rapidly standards organizations
move), in July of 1997. On the off chance that you have not seen
enough names yet for JavaScript, the standard version was called
ECMAScript, a name which still persists in some circles.
DHTML was a popular term in the late 1990s and early 2000s. It really
referred to any web page that had some sort of dynamic content that was
executed on the client side. It has fallen out of use, as the popularity of
JavaScript has made almost every page a dynamic one.
AJAX is a method by which small chunks of data are retrieved from the server
by a client instead of refreshing the entire page. The technology allows for
more interactive pages that avoid the jolt of full page reloads.
The popularity of GMail was the trigger for a change that had been
brewing for a while. Increasing JavaScript acceptance and
standardization pushed us past the tipping point for the acceptance of
JavaScript as a proper language. Up until that point, much of the use
of JavaScript was for performing minor changes to the page and for
validating form input. I joke with people that, in the early days of
JavaScript, the only function name which was used was Validate().
that when your language does not have a strong sense of connection
between the present and the future, this leads to more reckless
behavior in the present.
Tiobe 6
Github 1
What is more interesting is that you can see how the usage of
JavaScript is on the rise and it didn't stop growing in the last few
years.
"Any application that can be written in JavaScript, will eventually be written in JavaScript"
– Atwood's Law, Jeff Atwood
This insight has been proven to be correct time and time again. There
are now compilers, spreadsheets, word processors—you name it—all
written in JavaScript.
Since the GoF book, there has been a great proliferation of literature
dealing with enumerating and describing design patterns. There are
books on design patterns which are specific to certain domains and
books which deal with patterns for large enterprise systems. The
Wikipedia category for software design patterns contains 130 entries
for different design patterns. I would, however, argue that many of
the entries are not true design patterns but rather programming
paradigms.For the most part, design patterns are simple constructs
that don't need complicated support from libraries. While there do
exist pattern libraries for most languages, you need not go out and
spend a lot of money to purchase the libraries.Implement the patterns
as you find the need. Having an expensive library burning a hole in
your pocket encourages blindly applying patterns just to justify
having spent the money. Even if you did have the money, I'm not
aware of any libraries for JavaScript whose sole purpose is to provide
support for patterns. Of course, GitHub is a wealth of interesting
JavaScript projects, so there may well be a library on there of
which I'm unaware.
Anti-patterns
If there are common patterns to be found in good software design, are
there also patterns that can be found in bad software design?
Absolutely! There are a number of ways to do things incorrectly, but
most of them have been done before. It takes real creativity to screw
up in a hitherto unknown way.The shame of it is that it is very
difficult to remember all the ways in which people have gone wrong
over the years. At the end of many major projects, the team will sit
down and put together a document called Lessons Learned. This
document contains a list of things that could have gone better on the
project and may even outline some suggestions as to how these issues
can be avoided in the future. That these documents are only
constructed at the end of a project is unfortunate. By that time, many
of the key players have moved on and those who are left must try to
remember lessons from the early stages of the project, which could be
years ago. It is far better to construct the document as the project
progresses.Once complete, the document is led away ready for the
next project to make use of. At least, that is the theory. For the most
part, the document is led away and never used again. It is dif cult to
create lessons that are globally applicable. The lessons learned tend to
only be used for the current project or an exactly identical project,
which almost never happens.However, by looking at a number of
these documents from various projects, patterns start to emerge. It
was by following such an approach that William Brown, Raphael
Malveau, Skip McCormick, and Tom Mowbray, collectively known
as the Upstart Gang of Four in reference to the original Gang of Four,
wrote the initial book on anti-patterns. The book, AntiPatterns:
Refactoring Software, Architectures, and Projects in Crisis, outlined
anti-patterns not just for issues in the code, but also in the
management process which surrounds code.Patterns outlined include
such humorously named patterns as The Blob and Lava Flow. The
Blob, also known as the God object, is the pattern where one object
grows to take on the responsibility for vast swathes of the application
logic. Lava Flow is a pattern that emerges as a project ages and
nobody knows if a code is still used. Developers are nervous about
deleting the code because it might be used somewhere or may
become useful again. There are many other patterns described in the
book that are worth exploring. Just as with patterns, anti-patterns are
emergent from writing code, but in this case, code which gets out of
hand.This book will not cover JavaScript anti-patterns, but it is useful
to remember that one of the anti-patterns is an over-application of
design patterns.
Summary
Design patterns have a rich and interesting history. From their origin
as tools for helping to describe how to build the structures to allow
people to live together, they have grown to be applicable to a number
of domains.It has now been a decade since the seminal work on
applying design patterns to programming. Since then, a vast number
of new patterns have been developed. Some of these patterns are
general-purpose patterns such as those outlined in the GoF book, but
a larger number are very specific patterns which are designed for use
in a narrow domain.JavaScript also has an interesting history and is
really coming of age. With server-side JavaScript taking off and large
JavaScript applications becoming common, there is a need for more
diligence in building JavaScript applications. It is rare to see patterns
being properly exploited in most modern JavaScript code.Leaning on
the teachings provided by design patterns to build modern JavaScript
patterns gives one the best of both worlds. As Isaac Newton famously
wrote:"If I have seen further it is by standing on ye shoulders of
Giants."Patterns give us easily-accessible shoulders on which to
stand.In the next chapter, we will look at some techniques for
building structure into JavaScript. The inheritance system in
JavaScript is unlike that of most other object-oriented languages and
that provides us both opportunities and limits. We'll see how to build
classes and modules in the JavaScript world using ES6 and up.
Questions
1. What was the official name of Javascript the first time it was
released with Netscape?
2. What was the name of the alternative to Javascript released
with Internet Explorer 3?
3. How many ECMAScript versions of Javascript have been
released so far?
4. What was the turning point in the evolution of Javascript?
5. Where can you make use of Javascript?
6. What is the definition of a design pattern?
7. What other ways of working can be considered a sort of
design pattern?
8. What are the names of the major groups of the design
patterns?
9. When a design pattern can be considered an anti-pattern?
Further Reading
If after reading this chapter you still think that you need to
learn more about JavaScript this is one of the most handy
books you can read and practice with https://www.packtpub.com/w
eb-development/javascript-example
In the first part of this chapter, we will explain how to isolate our
code from the rest, for this we will explain the concept of namespaces
as well as how to isolate our code in modules to reuse the code in
different systems. Further, in this chapter, we will refresh how to use
ES5 to emulate object-oriented programming. Finally, at the end of
the chapter, we will go into detail on how to program object oriented
using ES6 and how to use ES6 modules natively.
In this section, we will talk about different ways that are used in
JavaScript programming to avoid conflicts with other teams work,
with third-party libraries and also allows us to group our code by
functionality while maintaining the single responsibility principle.
Why should we avoid global
variables?
In such a dynamic world as frontend development where every week
dozens of new contributions to the ecosystem can appear, each one
more interesting, sometimes we find ourselves with the problem of
having to use third-party libraries, either for analytics, plugins,
libraries or frameworks in our projects.
One of the main pitfalls we encounter is how to avoid problems or
conflicting names by adding an external library without having to
change anything in our base code.
com.companyName.packageName.subpackageName
ga Google Analytics
fb Facebook
$ jQuery
As you can see a short name is enough but it is also very easy to be
overwritten.
pkt.parser = {
parseHTML: function (html) {
// do something with html
},
parseCSS: function (css) {
// do something with css
}
};
Using namespaces, in the traditional way does not avoid the pollution
of the global ones since it continues adding an element to the global
object but it is very useful for the organization of our code in
packages.
IIFE – Immediate Invoked
Function Expression
Since in JavaScript the only way to keep something private or
isolated from the outside is through the closures, using an IIFE is the
best way to avoid contaminating the global ones, allowing a better
organization of the code and being able to expose only what is part of
the public API and keeping the implementation private.
In the following example code, you can see how we have created
countAttributes as a private function that is used in various methods
that will be exposed as public API of the parser object:
(function (namespace) {
'use strict';
function countAttributes(data) {
// Do something generic with data
}
namespace.parser = {
parseHTML: function (html) {
var attributesLen = countAttributes(html);
// do something with the HTML string
},
parseCSS: function (css) {
var attributesLen = countAttributes(css);
// do something with the CSS string
}
};
The UMD pattern typically attempts to offer compatibility with the most
popular script loaders of the day (e.g RequireJS amongst others). In many
cases it uses AMD (https://github.com/amdjs/amdjs-api/wiki/AMD) as a base, with special-
casing added to handle CommonJS
(http://wiki.commonjs.org/wiki/CommonJS) compatibility.
https://github.com/umdjs/umd
exports.default = 22;
});
The function where the module is defined is called factory and it will
be passed or executed depending of the environment. In AMD it will
be passed to the define function as a second argument and in any
other case it will be executed passing the object where our module
should be attached to.
If you want to learn more about the different ways of creating UMD check this
repository: https://github.com/umdjs/umd/tree/master/templates Various methods to create modules.
Module types
In the previous section we have explained how it is important to keep
our code isolated from the rest and we have presented some concepts
such as UMD(Universal Module Definition)and how to abstract the
way to create a module through our code depending on the execution
environment thus facilitating compatibility between the modular
systems currently existing.
Let's review the most common systems to create modules that we can
use in JavaScript
AMD
CommonJS
Revealing Module pattern
This way of creating a module is can be combined with a namespace
it can also be used to interact with other modules already existing in
the system.
With this format, we can maintain private functions and only expose
those we want to make public as well as through the global object
access to any object.
(function (global) {
var oContainer = null;
function setContainer(oCont) {
oContainer = oCont;
}
function addZero(nTime) {
return nTime < 10? '0' + nTime : nTime;
}
function getFormattedTime(dTime) {
return addZero(dTime.getHours()) + ':' +
addZero(dTime.getMinutes()) + ':' + addZero(dTime.getSeconds());
}
function render() {
oContainer.innerHTML = 'Test Module: ' + getFormattedTime(new
Date());
}
function destroy() {
oContainer.innerHTML = '';
}
global.pkt.time = {
render: function (oContainer) {
setContainer(oContainer);
render();
},
destroy: function () {
destroy();
}
};
}(this));
You can read more about Revealing Module Pattern visiting https://www.lookthink.c
om/blog/revealing-module-pattern
AMD
AMD stands for Asynchronous Module Definition. This system
works in such a way that when the module is defined, usually at the
moment of loading the file with the source code, it is not necessary
when it is going to be used, but rather when it is available for later
use in the system.
If you want to know more about how to use AMD modules using Require.js
please visit this link: http://requirejs.org/
In the following diagram you can see how the dependencies are
resolved:
In the following code, we will see how to create a module using
AMD and how this code can be used later in another module where it
will be loaded as a dependency.
// utilities.js
define('utilities', [], function () {
return {
countAttributes: function (string) {
// implementation code
}
};
});
// parser.js
define('parser', ['utilities'], function (utilities) {
return {
parseHTML: function (html) {
var attributesLen = utilities.countAttributes(html);
// do something with the HTML string
},
parseCSS: function (css) {
var attributesLen = utilities.countAttributes(css);
// do something with the CSS string
}
}
});
Module name
You can get deep into AMD reading the following post: https://en.wikipedia.org/wik
i/Asynchronous_module_definition
// utilities.js
var Utilities = {
trim: function (string) {
// implementation code should return a trimmed string.
}
};
// parser.js
var Utilities = require('./utilities);
// main.js
var parseHTML = require('parser').parseHTML;
console.log(parseHTML(html));
Note that CommonJS uses its own way of loading dependencies and
two different ways of exposing the public API to the other models
that may require it as a dependency.
require
module.exports
exports.<keyName>
In the case of the array, we see that there is one more prototype and
that we can navigate from prototype to prototype until we get an
error. The first prototype constructor is of the Array type and the
second one in the chain is Object.
You can also check that this string exists if you run the following:
console.log(arr instanceof Array);
// true
console.log(arr instanceof Object);
// true
function Mammal() {}
Mammal.prototype.makeSound = function () {
// do some sound
};
function Dog() {}
Dog.prototype = new Mammal();
Dog.prototype.constructor = Dog;
console.log(dog);
// Dog {}
console.log(dog.__proto__.constructor);
// ƒ Dog() {}
console.log(dog.__proto__.__proto__.constructor);
// ƒ Mammal() {}
console.log(dog.__proto__.__proto__.__proto__.constructor);
//ƒ Object() { [native code] }
The examples we have seen so far have been made in ES5 with the
intention of showing how object orientation can be emulated in ES5
through the prototype and to serve as an introductory basis for object
orientation with ES6.
OOP in ES6
One of the most important changes introduced in JavaScript in ES6,
related to OOP, has been the new way of creating the object
definition.
In this version, among many other things, the class, static and
extended keywords that already existed in other languages have been
added with the intention of facilitating communication between
developers and separating normal functions from object definitions.
With the use of the new system we have the following benefits:
class Mammal {
makeSound() {
// do some sound
}
}
console.log(dog); // Dog {}
console.log(dog.__proto__.constructor); // class Dog extends Mammal{}
console.log(dog.__proto__.__proto__.constructor);
/*
class Mammal {
makeSound() {
// do some sound
}
}
*/
console.log(dog.__proto__.__proto__.__proto__.constructor);
//ƒ Object() { [native code] }
We see how the prototype chain is maintained but now this code is
much cleaner and semantically correct.
In the following piece of code we will see how we can use the
constructor to encapsulate data that must remain isolated from the
user:
class Bicycle {
constructor() {
let _speed = 0;
let _speedStep = 10;
this.getSpeed = () => {
return _speed;
};
this.accelerate = () => {
_speed += _speedStep;
};
this.slowDown = () => {
_speed -= _speedStep;
};
}
}
console.log(bicycle._speed); // undefined
console.log(bicycle.getSpeed()); // 0
console.log(bicycle._speedStep); // undefined
console.log(bicycle.accelerate()); // undefined
console.log(bicycle._speed); // undefined
console.log(bicycle.getSpeed()); // 10
class Bicycle {
constructor() {
// This constructor must be the same as in the previous exercise.
}
start() {
// do something to prepare the bicycle.
console.log('Start riding the bicycle');
}
}
When we check if the start method of the instances is the same the
result is true because that method is in the Bicycle prototype but
when we check the same with the getSpeed method created in the
constructor we can see how they are different because they have
different references in memory.
Let's see in the following code how we can keep our attributes
private, without sacrificing performance, by adding our methods to
the prototype:
console.log(bicycle.getSpeed()); // 0
console.log(bicycle.accelerate()); // undefined
console.log(bicycle.getSpeed()); // 10
bicycle._speed = 100;
console.log(bicycle.getSpeed()); // 10
console.log(bicycle.accelerate()); // undefined
console.log(bicycle.getSpeed()); // 20
utilities.js
parser.js
main.js
console.log(parseHTML(html));
Summary
Object-oriented programming is a very opinionated programming
paradigm. In this chapter, we have started understanding how we can
organize our code in modules using IIFE and also introduced the
concept of UMD, AMD and CommonJS modules. We also learned
about how the prototype chain works in JavaScript and how to use it
in our benefit to emulate object-oriented programming in ES5 Finally
we have used ES6 to do object-oriented programming and at the end
of the chapter we have introduced the modular programming in ES6.
If after reading this chapter you still think that you need to
learn more about Object Oriented Programming in JavaScript
you can use the most updated version of the book with the
same name: https://www.packtpub.com/web-development/object-orient
ed-javascript-third-edition
)
aster/Chapter03
What is Functional
Programming?
The first thing you should know is that JavaScript is not a functional
programming language. Haskell, Miranda, R, Lisp, Scala, Scheme,
ML, Erlang are pure functional programming languages. Functional
programming languages, as the name implies, are function-based.
In this description you can notice words some new words I marked
not only because they introduce new concepts but also because they
are a great part of the functional programming we will examine in
this chapter.
More readable
More reusable
More testable
Less error-prone
Imperative programming
versus declarative
programming
Object-oriented programming is a programming paradigm in which
we work using imperative programming while functional
programming is a declarative form of programming.
Imperative programming is based on telling the computer HOW to
perform the instructions. We tell the computer what it should do, step
by step. Programming in this way is only intended to make the work
easier for the computer but leaves aside the most important part of the
process that is the developer.
The declarative programming focuses on WHAT we want to get and
we do not care how the result has been reached as long as the result is
the expected one.
The _drawer property is set using WeakMap to keep it private but also setting a
setter so we can access it as usual but keeping it immutable because it cannot
be set.
If you want to discover what it does, use the next module code:
Note that in this function we are not iterating but we use recursion calling
again the same function. We will examine it more in detail with other examples
but one important thing in functional programming is that you should never
iterate. On functional programming you have to forget about for, while, do-
while...
While the first bill in denominations is bigger than the difference, the
function will continue being executed. If the difference is lower or
equals to the bill, it will concatenate this bill to the result of executing
the function with a new difference.
Use this module to execute the method:
For brevity, we will not include the all inheritance but you have all the files in
the zip file with all the code examples.
cyprusBankBranch0128.accounts; // []
cyprusBankBranch0128.generateAccount(); // undefined[{…}]
cyprusBankBranch0128.accounts
/**
* [{
* accountId: "0000000000000000",
* ibanFormatted: "CY95 0020 0128 0000 0000 0000 0000"
* .}]
*/
Now let's get deep into the method and determine what else we can
find:
Should return always the same output on getting the same input
Now we will improve our code moving all the code to functional
programming.we can functions using functional programming.
In the next code section you will realize how we improved the code
to make it more functional:
return {
accountId,
ibanFormatted
};
};
this.accounts.push(newAccount);
}
}
The filesystem is employed for any reason because the filesystem can
change and then the result can be different
document.getElementById('my-button').addEventListener('click', function
(event) {
// Do something when the button is clicked.
});
let xmlhttp;
const requestData = () => {
xmlhttp = new XMLHttpRequest()
xmlhttp.onreadystatechange = processData;
xmlhttp.open('GET', 'http://some.external.resource', true);
xmlhttp.send();
}
const processData = () => {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200){
//process returned data
}
}
This is typically a cleaner approach and avoids performing complex
processing in line with another function.
However, you might be more familiar with the jQuery version of this,
which looks something like the following:
Likely you've seen this pattern in use before without even realizing it.
Passing functions into constructors as part of an options object is a
commonly used approach to providing extension hooks in JavaScript
libraries.
f2(a)=f(f1(a))
The classical higher-order functions that are provided by JavaScript
are:
Map
Reduce
Filter
In the next section we will get into these functions and how to use
them to improve our productivity.
MapReduce pattern
The MapReduce pattern is used mostly for Big Data processing. In
this section we will discover how to use map, reduce and filter to do
not iterate any more over our arrays to reduce the complexity of our
code and to use these higher-order functions to create more robust
applications.
In the image below you can view a diagram that explains how the
Map-Reduce pattern works:
The process of MapReduce pattern starts with some input that has a
lot of information but that is too complex to get anything clear from
it. The next step is to map each input to transform it in small pieces
that have their own identity. The last step is to use reduce to group the
data that are the same data so can be easily to use and to get
information.
By the end of this section we will implement a MapReduce example
so the concepts and this pattern can become more clear.
In this section we will explain how to use functions like map, reduce, filter but
there are other functions like some, foreach... but those have a different
purpose. Those functions and more where added in ECMAScript 5.1 . Note also
that if you system needs to be more fast using this functions can add more time
to process your arrays.
Map
The function map is one of the prototype methods of Array object.
Using map function on an array it iterates the array and per each
iteration it executes the function we passed as an argument. The
output of executing map over an array is a new array with the new
data applying the transformations to the original data:
let x = [a,b,c,d];
let y = x.map(ƒ);
x === y; // false
For more information you can visit MDN webpage about this method at: http
s://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
duplicateValuesInArray(numbers); // [2, 4, 6, 8]
This code cannot be functional because we are using a loop to iterate
over the array and because we are mutating the data, we are getting in
the input and last but not least because we are not returning any
value.
Using map to do the same will benefit us with less code and with a
more semantic and functional code.
let x = [a,b,c,d];
let y = x.reduce(ƒ, 0); //
/* ƒ receives 4 parameters
* 1. accumulator
* 2. current value
* 3. index
* 4. the array that reduce is iterating.
*
* ƒ returns the accumulated data
*/
x === y; // false
For more information you can visit MDN webpage about this method at: http
s://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce.
In the next snippet of code you can observe how to get the total
amount of summing up all the elements of one array using imperative
programming:
function sum(numbers) {
var total = 0;
for(let i = 0; i < numbers.length; i++) {
total += numbers[i];
}
return numbers;
}
sum(numbers); // 30;
sum(numbers); // 30
In the previous code you can realize how we created a new function
called add. The add function can be reused by other functions.
let x = [a,b,c,d];
let y = x.filter(ƒ); //
/* ƒ receives 3 parameters
* 1. item
* 3. index
* 4. the array that map is iterating.
*
* ƒ returns true or false.
*/
x === y; // false
For more information you can visit MDN webpage about this method at: http
s://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
In the next code, we are going filter to reduce the amount of lines
used in our code obtaining the same result:
The next code is the main file where we are making use of map,
reduce and filter using them as pipes, passing the result of one
execution to the next and to the next and so on:
/**
* getCappuccinoMuffinData
* Gets an array of bills and returns an object with all the expected
data.
* 1. It maps the object to flatten the items object in something more
manageable.
* 2. It filters the array to get a subset of elements that have
consumed a muffin
* and a cappuccino.
* 3. It reduces all the information to a single object with all the
data.
* @param {Array} bills
* @returns Object
*/
const getCappuccinoMuffinData = (bills) => {
return bills
.map(flattenBill)
.filter(filterByTwoMatches)
.reduce(finalReport.reducer, finalReport.initialValue);
};
console.log(getCappuccinoMuffinData(bills));
// { waiters: { Sophia :1, Lorenzo :2}, totalAmount :16.5,
averageTicket :5.5, billsAmount :3 }
In the next piece of code you can see an example of the data we are
going to use. You can find the whole file in the code zip file:
There are two reducers we will need in our exercise. The first to
flatten the items in a single productMatches variable and the second one
to get the final report object:
Here you have the code to flatten the bill used in the exercise:
/**
* flattenBill
* Gets a bill as a parameter and returns a new object with only
* the needed for the purpose.
* The object contains two properties:
* - waiter => Name of the waiter
* - items => A new object with the cost of the whole bill and how many
product matches the expected types
* @param {Object} bill
* @returns Object
*/
export const flattenBill = (bill) => {
return {
waiter: bill.waiter,
items: bill.items.reduce(flattenItems.reducer,
flattenItems.initialValue)
};
};
To know more about MapReduce pattern and its usage in Big Data you can
visit the following link:
https://en.wikipedia.org/wiki/MapReduce
Side-effects and immutability
One of the pillars of functional programming is the immutability of
the data. As we have seen in methods like map, reduce or filter those
methods get an array and return another array, a subset of the
elements of the array or a different object or primitive but without
changing the original value, these methods don't mutate the data.
If you don't want to get any side-effect, on executing your code, you
must use pure functions as you learned in the previous sections of this
chapter. Only using pure functions don't keep you, totally, save of
changing your input data and using map, reduce or filter it's very
costly.
When we use map, reduce or filter we are generating a completely
new array what it means that the computer will consume more
memory to keep those variables available.
In the next image you can see how map works and how it generates a
complete new array.
If you see the result of mapping the array, you can see how only two
elements of the array have changed but map is generating a complete
new array with all the elements even with these that didn't change.
With only five items it doesn't sound harmful for you system but what
could happen if instead of having an array of five items we have an
array with millions of items... Yes, it will become a problem. To solve
this problem in functional programming, we can use libraries, like
Mori or Immutable.js, that work using tree nodes making it more
efficient.
Using these libraries you can avoid the overhead of creating a new
array every time. When a new array is created it reuses the items of
the original links and links the new ones.
Immutable.js
To use functional programming more efficiently, you should use
these libraries and in this section we will introduce you to
Immutable.js.
If you want to learn more about Immutable.js and its different methods and
objects visit the following link.
https://facebook.github.io/immutable-js/
ES6 tail call optimizitation
Using functional programming is very helpful in many cases but it
can also become a problem when we need to execute a function
several times because it very expensive for the computer.
The most simple and most expensive function that will help us to
explain the problem is to get the factorial of a number:
factorial(10); // 3628800
factorial(11); // 39916800
factorial(1000000) // Maximum call stack size exceeded
Here we have two simple examples with small numbers but what will
happen if we pass a very high number is that the JavaScript engine
will not be able to handle it and it will get a Maximum call stack size
exceeded error.
In the next screenshot you can see how it throws a Range Error on
executing the factorial function with a very high value.
In ES6 they added a feature called tail call optimization. The tail call
optimization improvement is that if the return line is a function, it
will not deal with call stack issues. In the next code you can see how
we use this new feature together with the default properties to set an
accumulator:
If we use the factorial example, we can avoid to repeat calls that have
been already done and return the value stored from a previous call.
In the next piece of code you can find the implementation of factorial
memoized function using the memoize function we just created:
console.time();
factorialMemo(150); // 5.7133839564458575e+262
console.timeEnd(); // default: 0.094970703125ms
console.time();
factorialMemo(150); // 5.7133839564458575e+262
console.timeEnd(); // default: 0.008056640625ms
Lazy instantiation
If you go into a high-end coffee shop and place an order for some
overly complex beverage (Grande Chai Tea Latte, Three Pump, Skim
Milk, Lite Water, No Foam, Extra Hot anybody?), then that beverage
is going to be made on the fly and not in advance. Even if the coffee
shop knew what all the orders that were going to come in that day
would be, they would still not make all the beverages up front.
Firstly, because it would cause numerous ruined, cold beverages, and
secondly, it would be a very long time for the first customer to get
their order if they had to wait for all the orders of the day to be
completed.
This allows for breads to be rapidly added to the required breads list
without paying the price for each bread to be created.
Now, when pickUpBread is called, we'll actually create the breads:
You can see that the collection of actual breads is left until after the
pickup has been requested.
Being lazy can save you quite a bit of time in creating expensive
objects that end up never being used.
Currying – Partial application
Currying is the technique of translating the evaluation of a function
that takes multiple arguments into evaluating a sequence of functions,
each with a single argument.
Creating email accounts for different users and domains can be very
time consuming let's see how to use currying to make it simpler.
createGmailAccounts('john_doe'); // [email protected]
createGmailAccounts('jane_doe'); // [email protected]
createHotmailAccounts('jimmy_santillana'); //
[email protected]
createHotmailAccounts('gerard_picard'); // [email protected]
The benefits of currying are that you can use this function generators
to set the value at the beginning of the program and only execute the
returned function when it's needed. Currying allows to delegate the
execution during the execution of the program.
Composing
Composing is another benefit of functional programming allowing to
generate super functions using existing functions.
In the next piece of code you can see a simple example that requires
composing to reduce the complexity and imperative programming:
In the libraries section you have all the links to the libraries
available in JavaScript to facilitate functional programming,
this is also something you should read to use them or to get
inspired.
Over the last few years a number of different ideas have arisen in the
area of treating change as a stream of events. Given a starting
position and a stream of events it should be possible to figure out the
state of the system. By replaying this series of events we can recreate
the current state of the aggregate. This seems like a roundabout way
of storing the state of an object but it is actually very useful for a
number of situations. It is also stunningly useful for audit scenarios as
it is possible to pull the system back to the state it was in at any point
in time by simply halting the replay at a time index. How frequently
have you been asked, "why is the system in this state?", and you've
been unable to reply? With an event store the answer should be easy
to ascertain.
Streams
Filtering streams
Merging streams
Now you want to filter this array to only show you even numbers. In
modern JavaScript this is easily done through the use of the filter
function on the array:
For the most part this book avoids making use of any specific
JavaScript libraries. The idea is that patterns should be able to be
implemented with ease without a great deal of ceremony. However, in
this case we're actually going to make use of a library because
streams have a few nuances to their implementation for which we'd
like some syntactic niceties.
<body>
<button id="button"> Click Me!</button>
<span id="output"></span>
</body>
To this, let's add a quick click counter:
<script>
var counter = 0;
var button = document.getElementById('button');
var source = Rx.Observable.fromEvent(button, 'click');
var subscription = source.subscribe(function (e) {
counter++;
output.innerHTML = "Clicked " + counter + " time" + (counter >
1 ? "s" : "");
});
</script>
Here you can see we're creating a new stream of events from the
click event on the button. The newly created stream is commonly
referred to as a metastream. Whenever an event is emitted from the
source stream it is automatically manipulated and published, as
needed, to the metastream. We subscribe to this stream and increment
a counter. If we wanted to react to only the even numbered events, we
could do so by subscribing a second function to the stream:
Here you can see that we're applying a filter to the stream such that
the counter is distinct from the function which updates the screen.
Keeping a counter outside of the streams like this feels dirty, though,
doesn't it? Chances are that incrementing every other click isn't the
goal of this function anyway. It is much more likely that we would
like to run a function only on double click.
Here we take the click stream and buffer the stream using a
debounce to generate the boundaries of the buffer. Debouncing is a
term from the hardware world which means that we clean up a noisy
signal into a single event. When a physical button is pushed, there are
often a couple of additional high or low signals instead of the single
point signal we would like. In effect we eliminate repeated signals
which occur within a window. In this case we wait 250 ms before
firing an event to move to a new buffer. The buffer contains all the
events fired during the debouncing and passes on a list of them to the
next function in the chain. The map function generates a new stream
with the list length as the contents. Next, we filter the stream to show
only events with a value of 2 or more, that's two clicks or more. The
stream of events look like the following diagram:
Performing the same logic as this using traditional event listeners and
call-backs would be quite difficult. One could easily imagine a far
more complex workflow that would spiral out of control. FRP allows
for a more streamlined approach to handling events.
Filtering streams
As we saw in the preceding section, it is possible to filter a stream of
events and, from it produce a new stream of events. You might be
familiar with being able to filter items in an array. ES5 introduced a
number of new operators for arrays such as filter and some. The first
of these produces a new array containing only elements which match
the rule in the filter. Some is a similar function which simply
returns true if any element of the array matches. These same sorts of
functions are also supported on streams as well as functions you
might be familiar with from functional languages such as First and
Last. In addition to the functions which would make sense for arrays,
there are a number of time series based functions which make much
more sense when you consider that streams exist in time.
Rx.Observable
.FromEvent(button, "click")
.debounce(1000).subscribe((x)=>doSomething());
You might also find it that functions like Sample – which generates a
set of events from a time window. This is a very handy function when
we're dealing with observables which may produce a large number of
events. Consider an example from our example world of Westeros.
var deaths = [
{
Name:"Stannis",
Cause: "Cold"
},
{
Name: "Tyrion",
Cause: "Stabbing"
},
…
];
You can see we're using an array to simulate a stream of events. This can be
done with any stream and is a remarkably easy way to perform testing on
complex code. You can build a stream of events in an array and then publish
them with appropriate delays giving an accurate representation of anything
from a stream of events from the filesystem to user interactions.
function generateDeathsStream(deaths) {
return Rx.Observable.from(deaths).zip(Rx.Observable.interval(500),
(death,_)=>death);
}
In this code we zip together the deaths array with an interval stream
which fires every 500 ms. Because we're not super interested in the
interval event we simply discard it and project the item from the array
onwards.
Now we can sample this stream by simply taking a sample and then
subscribing to it. Here we're sampling every 1500 ms:
var counter = 0;
generateDeathsStream(deaths).subscribe((item) => { counter++ });
Merging streams
We've already seen the zip function that merges events one-to-one to
create a new stream but there are numerous other ways of combining
streams. A very simple example might be a page which has several
code paths which all want to perform a similar action. Perhaps we
have several actions all of which result in a status message being
updated:
Here you can see how the various streams are passed into the merge
function and the resulting merged stream:
While useful, this code doesn't seem to be particularly better than
simply calling the event handler directly, in fact it is longer than
necessary. However, consider that there are more sources of status
messages than just button pushes. We might want to have
asynchronous events also write out information. For instance, sending
a request to the server might also want to add status information.
Another fantastic application may be with web workers which run in
the background and communicate with the main thread using
messaging. For web based JavaScript applications this is how we
implement multithreaded applications. Let's see how that would look.
First we can create a stream from a worker role. In our example the
worker simply calculates the fibonacci sequence. We've added a
fourth button to our page and have it trigger the worker process:
var worker = Rx.DOM.fromWorker("worker.js");
button4Stream.subscribe(function (_) {
worker.onNext({ cmd: "start", number: 35 });
});
Now we can subscribe to the merged stream and combine it with all
the previous streams:
This all looks really nice but we don't want to clobber the users with
dozens of notifications at a time. We can throttle the stream of events
so that only a single toast shows up at a time by using the same
interval zip pattern we saw earlier. In this code we've replaced
our appendToOutput method with a call to a toast display library:
As you can see the code for this functionality is short and easy to
understand yet it contains a great deal of functionality.
Streams for multiplexing
One does not rise to a position of power on the King's council in
Westeros without being a master at building networks of spies. Often
the best spy is one who can respond the quickest. Similarly, we may
have some code which has the option of calling one of many different
services which can fulfill the same task. A great example would be a
credit card processor: it doesn't really matter which processor we use
as they're pretty much all the same.
You could even include a timeout in the amb call which would be
called to handle the eventuality that none of the processors responded
in time.
Best practices and trouble
shooting
There are a large number of different functions that can be applied to
streams. If you happen to decide on the RxJS library for your FRP
needs in JavaScript, many of the most common functions have been
implemented for you. More complex functions can often be written as
a chain of the included functions so try to think of a way to create the
functionality you want by chaining before writing your own
functions.
Perhaps the nicest thing about FRP is that it raises the level of
abstraction. You have to deal with less finicky process flow code and
can, instead, focus on the logical flow of the application.
https://en.wikipedia.org/wiki/Reactive_programming
https://en.wikipedia.org/wiki/Reactive_extensions
https://en.wikipedia.org/wiki/Functional_reactive_programming
Creational Patterns
Introduction
In the next three chapters we will review in depth the classic design
patterns or also known as the 'Gang of Four' patterns, as they were
four the authors of the first book to collect them all and written about.
Classic design patterns have been created focused in object-oriented
programming languages like Java, C++, C#. In this chapter we will
look at and understand patterns known as Creational Patterns and
how to use them in Javascript.
Singleton
Prototype
Constructor
Factory Method
Abstract Factory
Builder
Technical Requirements
You need to know how to object-oriented programming
works in Javascript (see Chapter 2)
)
-Edition/tree/master/Chapter05
Singleton
In JavaScript, unlike other languages that need a class, the simple
creation of an object using literal notation generates an object that can
be consider a single instance.
Configuration objects
Configuration objects
If you want to keep your object data immutable you can use Object.freeze.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
Prototype
Do not get confused with the Javascript inheritance feature of the
same name.
Let's imagine we are told to create the first system on Earth that
requires to start cloning human beings. The first thing we will need to
do is scan all what determines that human being, skin color, height,
weight, hair color. Once we have all this information we can use it to
create a clone with the same characteristics this is what Prototype
pattern does.
When to use it?
The Prototype design pattern is useful when:
If you want to learn more about Object.assign you can visit this link:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
const urls = {
packtpub: 'http://packtpub.com',
google: 'https://www.google.com'
};
const urls2 = {
wikipedia: 'http://en.wikipedia.org',
mdn: 'https://developer.mozilla.org'
};
/**
* {
* packtpub: "http://packtpub.com",
* google: "https://www.google.com",
* wikipedia: "http://en.wikipedia.org",
* mdn: "https://developer.mozilla.org"
* }
*/
Example
There are quite good examples, of Prototype design pattern, can be
found in DOM and a lot of different Javascript libraries and
framework as jQuery or Angular to name a few.
class HumanBeing {
constructor(config) {
this.skinColor = config.skinColor;
this.hairColor = config.hairColor;
this.height = config.height;
this.weight = config.weight;
this.gender = config.gender;
// And more data.
}
}
me === clone; // false but the data they contain is the same.
me === clone; // false but the data they contain is the same.
When not to use it?
You should not use Prototype design pattern when:
If you were refactoring your code you will also need the
instantiation of the product by invoking the new method you
created in the previous point, if not invoke the method where
the product should be created.
class PizzaStore {
createPizza() {
throw new Error("This method must be overwritten!");
}
orderPizza(type) {
let pizza = this.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}
}
As you can see the type of the pizza is defined on runtime just when
the orderPizza method is invoked.
Below you can find the definition of pizza that contains the four
methods used in our pizza store on ordering a pizza.
class Pizza {
constructor({ name = '', dough = '', sauce = '', toppings = []}) {
this.name = name;
this.dough = dough;
this.sauce = sauce;
this.toppings = toppings;
}
prepare() {
console.log("Preparing " + this.name);
console.log("Tossing dough...");
console.log("Adding sauce");
console.log("Adding toppings:");
bake() {
console.log("Bake for 25 minutes at 350");
}
cut() {
console.log("Cutting the pizza into diagonal slices");
}
box() {
console.log("Place pizza in official PizzaStore box");
}
getName() {
return this.name;
}
}
The pizza we just created is a very generic class so that it can be use
in our factory. Now it's time of creating our menu with a few pizzas
of diverse taste.
const PIZZAS = {
cheese: CheesePizza,
clam: ClamPizza
};
We have set everything needed to start our pizza business lets' see
how it works.
class Pizza {
constructor({ name = '', dough = '', sauce = '', veggies = [], cheese
= '', pepperoni = '', clams = '' }) {
this.name = name;
this.dough = dough;
this.sauce = sauce;
this.veggies = veggies;
this.cheese = cheese;
this.pepperoni = pepperoni;
this.clams = clams;
}
class Pizza {
constructor({ name = '', dough = '', sauce = '', toppings = []}) {
this.name = name;
this.dough = dough;
this.sauce = sauce;
this.toppings = toppings;
}
// Other methods.
}
Now every type of topping has been separated by its kind instead of
putting them all together and this makes simpler to track what kind of
topping is more used or if we are doing anything wrong.
In the next snippet of code you can see how we created an abstract
class* to create different ingredients of our pizza.
createSauce() {
throw new Error("This method must be overwritten!");
}
createCheese() {
throw new Error("This method must be overwritten!");
}
createVeggies() {
throw new Error("This method must be overwritten!");
}
createPepperoni() {
throw new Error("This method must be overwritten!");
}
createClam() {
throw new Error("This method must be overwritten!");
}
}
createSauce() {
return new MarinaraSauce();
}
createCheese() {
return new ReggianoCheese();
}
createVeggies() {
return [new Garlic(), new Mushroom(), new RedPepper()];
}
createPepperoni() {}
createClam() {}
}
Below you can see how we pass the ingredientFactory to the constructor
of our pizza so we can easily change the recipe of our pizza only
changing it from outside of our pizza class.
prepare() {
let ingredientFactory = this.ingredientFactory;
console.log("Preparing " + this.name);
this.dough = ingredientFactory.createDough();
this.sauce = ingredientFactory.createSauce();
this.cheese = ingredientFactory.createCheese();
}
}
After this line you will find how to create a product class.
class Garlic {}
The problem even not being the same can give problems because if
there is something to be added the class is required to be modified to
add the new flavours or options.
class Meal {
constructor(id, name, calories = 0, servingSize = 0, fat = 0,
description = 'default description') {
this.id = id;
this.name = name;
this.calories = calories;
this.servingSize = servingSize;
this.fat = fat;
this.description = description;
}
}
class Item {
get name() {
throw new Error('This method should be implemented');
}
get packing() {
throw new Error('This method should be implemented');
}
get price() {
throw new Error('This method should be implemented');
}
}
class Packing {
pack() {
throw new Error('This method should be implemented');
}
}
Once we have the abstract classes we have to create the
implementation of our products and type of packaging.
Packing types:
Item types:
Below we create the Meal class to add the items, show the items and
get the final cost.
The next step is to create the builder director with a method per each
type of product flavour we need to create, in our case each builder
method should create a new menu meal.
class MealBuilder {
prepareVeganMeal() {
var meal = new Meal();
meal.addItem(new VeganBurger());
meal.addItem(new Water());
meal.addItem(new Salad());
return meal;
}
prepareNonVeganMeal() {
var meal = new Meal();
meal.addItem(new BeefBurger());
meal.addItem(new Coke());
meal.addItem(new Fries());
return meal;
}
prepareDeluxeMeal() {
var meal = new Meal();
meal.addItem(new KobeBurger());
meal.addItem(new Champagne());
meal.addItem(new Crudettes());
return meal;
}
}
If you have to create a product that requires to create the product adding
ingredients it's better if you use Decorator Pattern. See next chapter about the
implementation.
Pros
Building products step by step.
You have learn how to use Singleton to create a common entry point
for every piece in the system but also when not to use it. We have
used Prototype to create a clone of our objects. The two factory
patterns have simplified what could be very difficult to do if we don't
know what type of class we are going to use. At the end of the
chapter we used Builder to understand how to create a product step
by step.
Adapter
Composite
Decorator
Bridge
Façade
Flyweight
Proxy
Technical Requirements
You need to know how to object-oriented programming
works in Javascript (see Chapter 2)
)
-Edition/tree/master/Chapter06
Adapter
The Adapter pattern allows objects with incompatible interfaces to
collaborate.
Once you have detected the two actors you have to:
class Duck {
constructor() {}
fly() {
throw new Error('This method must be overwritten!');
}
quack() {
throw new Error('This method must be overwritten!');
}
}
const MAX_FLIES = 5;
fly() {
for (let index = 0; index < MAX_FLIES; index++) {
this.oTurkey.fly();
}
}
quack() {
this.oTurkey.gobble();
}
}
The adapter class should extend of our abstract class and implement
the method by it's own.
class MenuComponent {
constructor(name = '', description = '', isVegetarian = false, price
= 0) {
this.name = name;
this.description = description;
this._isVegetarian = isVegetarian;
this.price = price;
}
getName() {
return this.name;
}
getDescription() {
return this.description;
}
getPrice() {
return this.price;
}
isVegetarian() {
return this._isVegetarian;
}
print() {
shouldBeOverwritten();
}
add() {
shouldBeOverwritten();
}
remove() {
shouldBeOverwritten();
}
getChild() {
shouldBeOverwritten();
}
}
In the Composite pattern all the rest of the elements, simple and
complex, will require to extend from this abstract class.
add(menuComponent) {
this.menuComponents.push(menuComponent);
}
remove(menuComponent) {
this.menuComponents = this.menuComponents.filter(component => {
return component !== component;
});
}
getChild(index) {
return this.menuComponents[index];
}
getName() {
return this.name;
}
getDescription() {
return this.description;
}
print() {
console.log(this.getName() + ": " + this.getDescription());
console.log("--------------------------------------------");
this.menuComponents.forEach(component => {
component.print();
});
}
}
MenuItem is our simple element class that will be used to create more
specific menu items.
One of the most common problems that Decorator pattern can solve
is when in one system the different possibilities of creating objects
can become infinite.
A real-world example can be an ice cream shop where there are many
different syrups of different flavours as well as a bunch of different
ice cream flavours and depending of the customer there are infinite
possibilities that you will never predict in advance.
When to use it?
When using Composite pattern is not enough because your system
can not be understood as a tree and you have to add and or remove
responsibilities to or from an object dynamically keeping these
objects compatible with the rest of your system code. Note that
because the complexity you cannot use inheritance to create the
different use cases.
How to implement it?
In Decorator pattern every object acts as a decorator or wrapper.
Review your system and detect what is the element that can
be represented as the primary component as well as all the
other optional extensions.
class Beverage {
constructor(description = 'Unknown beverage') {
this.description = description;
}
getDescription() {
return this.description;
}
cost() {
throw new Error("This method must be overwritten!");
}
}
In the Decorator Pattern any element of the system should exist from
the basic item as you can see in our CondimentDecorator.
Espresso implementation:
getDescription() {
return this.beverage.getDescription() + ", Mocha";
}
cost() {
return 0.20 + this.beverage.cost();
}
}
getDescription() {
return this.beverage.getDescription() + ', Whip';
}
cost() {
return 0.60 + this.beverage.cost();
}
}
When there are not many different options and they can be
handled by specific classes using or not inheritance.
Pros
Simplify your class system.
Anything that was not relevant for the client will become
different use cases for the client.
First, as we know that our class will be painted with different colors
we need to create our basic Color class because its interface will be
implemented in the car classes.
class Color {
applyColor() {
throw new Error("This method should be overwritten");
}
}
Once we have the Color class we need to create our basic Car class.
Note that we pass the color instance through the constructor as well
as declaring the applyColor method.
class Car {
constructor(name, description, price, places = 2, color, brand =
'Cartisfaction') {
this.brand = brand;
this.name = name;
this.description = description;
this.price = price;
this.places = places;
this.color = color;
}
applyColor() {
console.log(`${this.name} car painted with color
${this.color.applyColor()}`);
}
}
Our company has three different cars to cover almost all the different
customers in the market.
4 x 4 adventure car is our car for all the people that likes the
adventure and doesn't care about creating new paths
Urban car is our car for all the other needs. Is a small car that
saves money to it's owner and that was designed to be,
mainly, used at the city.
Once we have all our car classes we have to implement the different
color catalog we want to offer to our customers to purchase his or her
car according to his or her taste.
In the next three snippets of code you can see two color
implementations.
Watch a movie
Amplifier
MP3 Player
BlueRay Player
Popcorn Popper
Projector
Screen
Theater Lights
Tuner
When we analize every element we detect that all of them are
elements that can be switch on/off and some of them are players. So
we abstract those two behaviors in two different abstract classes.
off() {
throw new Error('This method should be overwritten!');
}
};
play() {
throw new Error('This method should be overwritten!');
}
stop() {
throw new Error('This method should be overwritten!');
}
};
For the sake of the simplicity I will only show two different
implementations, one implementing Switchable and another one
implementing Switchable and Playable behavior. The rest of the code
will be available in the Github repository.
on() {
console.log("Amplifier is on!");
}
off() {
console.log("Amplifier is off!");
}
setVolume(volume) {
this.volume = volume;
console.log("Volume change to " + volume);
}
setDvdPlayer(dvdPlayer) {
this.dvdPlayer = dvdPlayer;
console.log("Plugged DVD Player to Amplifier!");
}
setCdPlayer(cdPlayer) {
this.cdPlayer = cdPlayer;
console.log("Plugged Cd Player to Amplifier!");
}
setTuner(tuner) {
this.tuner = tuner;
console.log("Plugged on Tuner to Amplifier!");
}
setSurroundSound() {
this.surroundSound = true;
console.log("Surround Mode is active!");
}
setStereoSound() {
this.stereoSound = true;
console.log("Stereo Mode is active!");
}
}
off() {
console.log("BlueRay player is off!");
}
eject() {
console.log("Eject BlueRay!");
}
play(movie) {
console.log("Playing " + movie.name);
}
stop() {
console.log("Stop BlueRay player!");
}
}
class HomeTheaterFacade {
constructor({ amplifier = null, tuner = null, brPlayer = null,
mp3Player = null, projector = null, theaterLights = null, screen =
null, popcornPopper = null}) {
this.amplifier = amplifier;
this.tuner = tuner;
this.brPlayer = brPlayer;
this.mp3Player = mp3Player;
this.projector = projector;
this.theaterLights = theaterLights;
this.screen = screen;
this.popcornPopper = popcornPopper;
}
watchMovie(movie) {
console.log('Get ready to watch a movie...');
this.popcornPopper.on();
this.popcornPopper.pop();
this.theaterLights.off();
this.screen.down();
this.projector.on();
this.projector.setWideScreenMode();
this.amplifier.on();
this.amplifier.setDvdPlayer(this.dvdPlayer);
this.amplifier.setSurroundSound();
this.amplifier.setVolume(5);
this.brPlayer.on();
this.brPlayer.play(movie);
}
endMovie() {
console.log("Shutting movie theater down...");
this.popcornPopper.off();
this.theaterLights.on();
this.screen.up();
this.projector.off();
this.amplifier.off();
this.brPlayer.stop();
this.brPlayer.eject();
this.brPlayer.off();
}
listenToMP3(playlist) {
console.log("Start listening your music...");
this.amplifier.on();
this.amplifier.setCdPlayer(this.cdPlayer);
this.amplifier.setStereoSound();
this.amplifier.setVolume(5);
this.mp3Player.on();
this.mp3Player.play(playlist);
}
endMP3() {
console.log("End listening your music or the Cd has finished!");
this.amplifier.off();
this.mp3Player.stop();
this.mp3Player.off();
}
listenToRadio() {
console.log("Start listening your favorite radio station...");
this.amplifier.on();
this.amplifier.setTuner(this.tuner);
this.amplifier.setStereoSound();
this.amplifier.setVolume(5);
this.tuner.on();
this.tuner.setFm();
this.tuner.setFrequency(90.9);
}
endRadio() {
console.log("End listening your favorite radio station...");
this.amplifier.off();
this.tuner.off();
}
}
If you want to see this example running you can visit: https://runkit.co
m/tcorral/facade
When not to use it?
When the system doesn't have complex subsystems or when the
system is not complex enough.
Pros
Clients don't know about subsystem components
When because the huge amount of objects the memory can not
support it.
How to implement it?
Analyze your objects and differentiate between fields with
different states:
First, we create the Tree class that contains the extrinsic data, data
that changes per each object.
class Tree {
constructor(x = 0, y = 0, treeType) {
privateX.set(this, x);
privateY.set(this, y);
privateTreeType.set(this, treeType);
}
get x() {
return privateX.get(this);
}
get y() {
return privateY.get(this);
}
get treeType() {
return privateTreeType.get(this);
}
render(canvas) {
const context = canvas.getContext("2d");
this.treeType.render(context, this.x, this.y);
}
}
Now we have to create the TreeType class that will contain the
intrinsic data, data that doesn't change between objects.
const privateName = new WeakMap();
const privateColor = new WeakMap();
const privateTreeConfig = new WeakMap();
class TreeType {
constructor(name, color, treeConfig) {
privateName.set(this, name);
privateColor.set(this, color);
privateTreeConfig.set(this, treeConfig);
}
get name() {
return privateName.set(this);
}
get color() {
return privateColor.set(this);
}
get treeConfig() {
return privateTreeConfig.set(this);
}
render(context, x, y) {
context.fillStyle = "black";
context.fillRect(x - 1, y, 3, 5);
}
}
Once we have the intrinsic data and extrinsic data stored in both
different classes we have to encapsulate the creation of flyweight
elements in a factory.
Proxy
Virtual Proxy.
In the next piece of code we have to deal with a heavy object called
PublicLibrary this object has access to a lot of information, catalogs of
data and use it from our system will have a performance impact on it.
class PublicLibrary {
constructor(books) {
this.catalog = {};
this.setCatalogFromBooks(books);
}
setCatalogFromBooks(books) {
books.forEach(book => {
this.catalog[book.getIsbn()] = {
book: book,
available: true
};
});
}
findBooks(query) {
console.log("Enter findBooks PublicLibrary");
let results = [];
for(let book of this.catalog) {
if (query.match(book.getTitle()) ||
query.match(book.getAuthor())) {
results.push(book);
}
}
return results;
}
checkoutBook(book) {
let isbn = book.getIsbn();
book = this.catalog[isbn];
if(book) {
if(book.available) {
book.available = false;
return book;
} else {
throw new Error('PublicLibrary: book ' + book.getTitle() + ' is
not currently available.');
}
} else {
throw new Error('PublicLibrary: book ' + book.getTitle() + ' not
found.');
}
}
returnBook(book) {
let isbn = book.getIsbn();
book = this.catalog[isbn];
if(book) {
book.available = true;
} else {
throw new Error('PublicLibrary: book ' + book.getTitle() + ' not
found.');
}
}
}
class PublicLibraryProxy {
constructor(catalog = []) {
this.library = new PublicLibrary(catalog);
}
findBooks(query) {
console.log("Enter findBooks PublicLibraryProxy");
return this.library.findBooks(query);
}
checkoutBook(book) {
return this.library.checkoutBook(book);
}
returnBook(book) {
return this.library.returnBook(book);
}
}
function initializeLibrary(instance) {
if (instance.library === null) {
instance.library = new PublicLibrary(instance.catalog);
}
}
class PublicLibraryVirtualProxy {
constructor(catalog = []) {
this.library = null;
this.catalog = catalog;
}
findBooks(query) {
console.log("Enter findBooks PublicLibraryVirtualProxy");
initializeLibrary(this);
return this.library.findBooks(query);
}
checkoutBook(book) {
initializeLibrary(this);
return this.library.checkoutBook(book);
}
returnBook(book) {
initializeLibrary(this);
return this.library.returnBook(book);
}
}
You can see both examples running in the next two links.
Lifecycle management.
You have learn how to use Adapter so you can use incompatible
objects in your system without noticing it. We have created
Composite pattern that makes easier to understand how tree structures
work. Decorator Pattern has demonstrated to be very useful when the
different flavors of one or more products cannot be managed by
inheritance. With a simple example of a Home Theater we got deeply
in the Facade pattern demonstrating how to simplify the interface to
the user and encapsulating the implementation from the client. With
the Flyweight we have, drastically, reduced the amount of memory
we can use in our applications when we have to deal with thousands
of instances of objects. And last but not least we have used Proxy
pattern to work with heavyweight objects.
In order to know more about the classic design patterns and the GoF
you can visit: http://wiki.c2.com/?GangOfFour
Behavioural Patterns
Coming soon...
Performance patterns
Coming soon...
Asynchronous patterns
Coming soon...
Patterns for Testing
Coming soon...
Advanced Patterns
Coming soon...
Application Patterns
Coming soon...
Web Patterns
Coming soon...
Messaging Patterns
Coming soon...
Micro-services
Coming soon...
ES2015/2017/2018 Solutions
Today and the Road ahead
Coming soon...
ES2019 What is ESNEXT
Coming soon...