• United States+1
  • United Kingdom+44
  • Afghanistan (‫افغانستان‬‎)+93
  • Albania (Shqipëri)+355
  • Algeria (‫الجزائر‬‎)+213
  • American Samoa+1684
  • Andorra+376
  • Angola+244
  • Anguilla+1264
  • Antigua and Barbuda+1268
  • Argentina+54
  • Armenia (Հայաստան)+374
  • Aruba+297
  • Australia+61
  • Austria (Österreich)+43
  • Azerbaijan (Azərbaycan)+994
  • Bahamas+1242
  • Bahrain (‫البحرين‬‎)+973
  • Bangladesh (বাংলাদেশ)+880
  • Barbados+1246
  • Belarus (Беларусь)+375
  • Belgium (België)+32
  • Belize+501
  • Benin (Bénin)+229
  • Bermuda+1441
  • Bhutan (འབྲུག)+975
  • Bolivia+591
  • Bosnia and Herzegovina (Босна и Херцеговина)+387
  • Botswana+267
  • Brazil (Brasil)+55
  • British Indian Ocean Territory+246
  • British Virgin Islands+1284
  • Brunei+673
  • Bulgaria (България)+359
  • Burkina Faso+226
  • Burundi (Uburundi)+257
  • Cambodia (កម្ពុជា)+855
  • Cameroon (Cameroun)+237
  • Canada+1
  • Cape Verde (Kabu Verdi)+238
  • Caribbean Netherlands+599
  • Cayman Islands+1345
  • Central African Republic (République centrafricaine)+236
  • Chad (Tchad)+235
  • Chile+56
  • China (中国)+86
  • Christmas Island+61
  • Cocos (Keeling) Islands+61
  • Colombia+57
  • Comoros (‫جزر القمر‬‎)+269
  • Congo (DRC) (Jamhuri ya Kidemokrasia ya Kongo)+243
  • Congo (Republic) (Congo-Brazzaville)+242
  • Cook Islands+682
  • Costa Rica+506
  • Côte d’Ivoire+225
  • Croatia (Hrvatska)+385
  • Cuba+53
  • Curaçao+599
  • Cyprus (Κύπρος)+357
  • Czech Republic (Česká republika)+420
  • Denmark (Danmark)+45
  • Djibouti+253
  • Dominica+1767
  • Dominican Republic (República Dominicana)+1
  • Ecuador+593
  • Egypt (‫مصر‬‎)+20
  • El Salvador+503
  • Equatorial Guinea (Guinea Ecuatorial)+240
  • Eritrea+291
  • Estonia (Eesti)+372
  • Ethiopia+251
  • Falkland Islands (Islas Malvinas)+500
  • Faroe Islands (Føroyar)+298
  • Fiji+679
  • Finland (Suomi)+358
  • France+33
  • French Guiana (Guyane française)+594
  • French Polynesia (Polynésie française)+689
  • Gabon+241
  • Gambia+220
  • Georgia (საქართველო)+995
  • Germany (Deutschland)+49
  • Ghana (Gaana)+233
  • Gibraltar+350
  • Greece (Ελλάδα)+30
  • Greenland (Kalaallit Nunaat)+299
  • Grenada+1473
  • Guadeloupe+590
  • Guam+1671
  • Guatemala+502
  • Guernsey+44
  • Guinea (Guinée)+224
  • Guinea-Bissau (Guiné Bissau)+245
  • Guyana+592
  • Haiti+509
  • Honduras+504
  • Hong Kong (香港)+852
  • Hungary (Magyarország)+36
  • Iceland (Ísland)+354
  • India (भारत)+91
  • Indonesia+62
  • Iran (‫ایران‬‎)+98
  • Iraq (‫العراق‬‎)+964
  • Ireland+353
  • Isle of Man+44
  • Israel (‫ישראל‬‎)+972
  • Italy (Italia)+39
  • Jamaica+1876
  • Japan (日本)+81
  • Jersey+44
  • Jordan (‫الأردن‬‎)+962
  • Kazakhstan (Казахстан)+7
  • Kenya+254
  • Kiribati+686
  • Kosovo+383
  • Kuwait (‫الكويت‬‎)+965
  • Kyrgyzstan (Кыргызстан)+996
  • Laos (ລາວ)+856
  • Latvia (Latvija)+371
  • Lebanon (‫لبنان‬‎)+961
  • Lesotho+266
  • Liberia+231
  • Libya (‫ليبيا‬‎)+218
  • Liechtenstein+423
  • Lithuania (Lietuva)+370
  • Luxembourg+352
  • Macau (澳門)+853
  • Macedonia (FYROM) (Македонија)+389
  • Madagascar (Madagasikara)+261
  • Malawi+265
  • Malaysia+60
  • Maldives+960
  • Mali+223
  • Malta+356
  • Marshall Islands+692
  • Martinique+596
  • Mauritania (‫موريتانيا‬‎)+222
  • Mauritius (Moris)+230
  • Mayotte+262
  • Mexico (México)+52
  • Micronesia+691
  • Moldova (Republica Moldova)+373
  • Monaco+377
  • Mongolia (Монгол)+976
  • Montenegro (Crna Gora)+382
  • Montserrat+1664
  • Morocco (‫المغرب‬‎)+212
  • Mozambique (Moçambique)+258
  • Myanmar (Burma) (မြန်မာ)+95
  • Namibia (Namibië)+264
  • Nauru+674
  • Nepal (नेपाल)+977
  • Netherlands (Nederland)+31
  • New Caledonia (Nouvelle-Calédonie)+687
  • New Zealand+64
  • Nicaragua+505
  • Niger (Nijar)+227
  • Nigeria+234
  • Niue+683
  • Norfolk Island+672
  • North Korea (조선 민주주의 인민 공화국)+850
  • Northern Mariana Islands+1670
  • Norway (Norge)+47
  • Oman (‫عُمان‬‎)+968
  • Pakistan (‫پاکستان‬‎)+92
  • Palau+680
  • Palestine (‫فلسطين‬‎)+970
  • Panama (Panamá)+507
  • Papua New Guinea+675
  • Paraguay+595
  • Peru (Perú)+51
  • Philippines+63
  • Poland (Polska)+48
  • Portugal+351
  • Puerto Rico+1
  • Qatar (‫قطر‬‎)+974
  • Réunion (La Réunion)+262
  • Romania (România)+40
  • Russia (Россия)+7
  • Rwanda+250
  • Saint Barthélemy (Saint-Barthélemy)+590
  • Saint Helena+290
  • Saint Kitts and Nevis+1869
  • Saint Lucia+1758
  • Saint Martin (Saint-Martin (partie française))+590
  • Saint Pierre and Miquelon (Saint-Pierre-et-Miquelon)+508
  • Saint Vincent and the Grenadines+1784
  • Samoa+685
  • San Marino+378
  • São Tomé and Príncipe (São Tomé e Príncipe)+239
  • Saudi Arabia (‫المملكة العربية السعودية‬‎)+966
  • Senegal (Sénégal)+221
  • Serbia (Србија)+381
  • Seychelles+248
  • Sierra Leone+232
  • Singapore+65
  • Sint Maarten+1721
  • Slovakia (Slovensko)+421
  • Slovenia (Slovenija)+386
  • Solomon Islands+677
  • Somalia (Soomaaliya)+252
  • South Africa+27
  • South Korea (대한민국)+82
  • South Sudan (‫جنوب السودان‬‎)+211
  • Spain (España)+34
  • Sri Lanka (ශ්‍රී ලංකාව)+94
  • Sudan (‫السودان‬‎)+249
  • Suriname+597
  • Svalbard and Jan Mayen+47
  • Swaziland+268
  • Sweden (Sverige)+46
  • Switzerland (Schweiz)+41
  • Syria (‫سوريا‬‎)+963
  • Taiwan (台灣)+886
  • Tajikistan+992
  • Tanzania+255
  • Thailand (ไทย)+66
  • Timor-Leste+670
  • Togo+228
  • Tokelau+690
  • Tonga+676
  • Trinidad and Tobago+1868
  • Tunisia (‫تونس‬‎)+216
  • Turkey (Türkiye)+90
  • Turkmenistan+993
  • Turks and Caicos Islands+1649
  • Tuvalu+688
  • U.S. Virgin Islands+1340
  • Uganda+256
  • Ukraine (Україна)+380
  • United Arab Emirates (‫الإمارات العربية المتحدة‬‎)+971
  • United Kingdom+44
  • United States+1
  • Uruguay+598
  • Uzbekistan (Oʻzbekiston)+998
  • Vanuatu+678
  • Vatican City (Città del Vaticano)+39
  • Venezuela+58
  • Vietnam (Việt Nam)+84
  • Wallis and Futuna+681
  • Western Sahara (‫الصحراء الغربية‬‎)+212
  • Yemen (‫اليمن‬‎)+967
  • Zambia+260
  • Zimbabwe+263
  • Åland Islands+358
Thanks! We'll be in touch in the next 12 hours
Oops! Something went wrong while submitting the form.

The 7 Most Useful Design Patterns in ES6 (and how you can implement them)

Shubham Rajodiya

Full-stack Development

After spending a couple of years in JavaScript development, I’ve realized how incredibly important design patterns are, in modern JavaScript (ES6). And I'd love to share my experience and knowledge on the subject, hoping you’d make this a critical part of your development process as well.

Note: All the examples covered in this post are implemented with ES6 features, but you can also integrate the design patterns with ES5.

At Velotio, we always follow best practices to achieve highly maintainable and more robust code. And we are strong believers of using design patterns as one of the best ways to write clean code. 

In the post below, I’ve listed the most useful design patterns I’ve implemented so far and how you can implement them too:

1. Module

The module pattern simply allows you to keep units of code cleanly separated and organized. 

Modules promote encapsulation, which means the variables and functions are kept private inside the module body and can't be overwritten.

Creating a module in ES6 is quite simple.

// Addition module
export const sum = (num1, num2) => num1 + num2;
view raw modules.js hosted with ❤ by GitHub

// usage
import { sum } from 'modules/sum';
const result = sum(20, 30); // 50
view raw app.js hosted with ❤ by GitHub

ES6 also allows us to export the module as default. The following example gives you a better understanding of this.

// All the variables and functions which are not exported are private within the module and cannot be used outside. Only the exported members are public and can be used by importing them.
// Here the businessList is private member to city module
const businessList = new WeakMap();
// Here City uses the businessList member as it’s in same module
class City {
constructor() {
businessList.set(this, ['Pizza Hut', 'Dominos', 'Street Pizza']);
}
// public method to access the private ‘businessList’
getBusinessList() {
return businessList.get(this);
}
// public method to add business to ‘businessList’
addBusiness(business) {
businessList.get(this).push(business);
}
}
// export the City class as default module
export default City;
view raw city.js hosted with ❤ by GitHub

// usage
import City from 'modules/city';
const city = new City();
city.getBusinessList();
view raw CityExample.js hosted with ❤ by GitHub

There is a great article written on the features of ES6 modules here.

2. Factory

Imagine creating a Notification Management application where your application currently only allows for a notification through Email, so most of the code lives inside the EmailNotification class. And now there is a new requirement for PushNotifications. So, to implement the PushNotifications, you have to do a lot of work as your application is mostly coupled with the EmailNotification. You will repeat the same thing for future implementations.

To solve this complexity, we will delegate the object creation to another object called factory.

class PushNotification {
constructor(sendTo, message) {
this.sendTo = sendTo;
this.message = message;
}
}
class EmailNotification {
constructor(sendTo, cc, emailContent) {
this.sendTo = sendTo;
this.cc = cc;
this.emailContent = emailContent;
}
}
// Notification Factory
class NotificationFactory {
createNotification(type, props) {
switch (type) {
case 'email':
return new EmailNotification(props.sendTo, props.cc, props.emailContent);
case 'push':
return new PushNotification(props.sendTo, props.message);
}
}
}
// usage
const factory = new NotificationFactory();
// create email notification
const emailNotification = factory.createNotification('email', {
sendTo: 'receiver@domain.com',
cc: 'test@domain.com',
emailContent: 'This is the email content to be delivered.!',
});
// create push notification
const pushNotification = factory.createNotification('push', {
sendTo: 'receiver-device-id',
message: 'The push notification message',
});

3. Observer

(Also known as the publish/subscribe pattern.)

An observer pattern maintains the list of subscribers so that whenever an event occurs, it will notify them. An observer can also remove the subscriber if the subscriber no longer wishes to be notified.

On YouTube, many times, the channels we’re subscribed to will notify us whenever a new video is uploaded.

// Publisher
class Video {
constructor(observable, name, content) {
this.observable = observable;
this.name = name;
this.content = content;
// publish the ‘video-uploaded’ event
this.observable.publish('video-uploaded', {
name,
content,
});
}
}
// Subscriber
class User {
constructor(observable) {
this.observable = observable;
this.intrestedVideos = [];
// subscribe with the event naame and the call back function
this.observable.subscribe('video-uploaded', this.addVideo.bind(this));
}
addVideo(video) {
this.intrestedVideos.push(video);
}
}
// Observer
class Observable {
constructor() {
this.handlers = [];
}
subscribe(event, handler) {
this.handlers[event] = this.handlers[event] || [];
this.handlers[event].push(handler);
}
publish(event, eventData) {
const eventHandlers = this.handlers[event];
if (eventHandlers) {
for (var i = 0, l = eventHandlers.length; i < l; ++i) {
eventHandlers[i].call({}, eventData);
}
}
}
}
// usage
const observable = new Observable();
const user = new User(observable);
const video = new Video(observable, 'ES6 Design Patterns', videoFile);
view raw pub-sub.js hosted with ❤ by GitHub

4. Mediator

The mediator pattern provides a unified interface through which different components of an application can communicate with each other.

If a system appears to have too many direct relationships between components, it may be time to have a central point of control that components communicate through instead. 

The mediator promotes loose coupling. 

A real-time analogy could be a traffic light signal that handles which vehicles can go and stop, as all the communications are controlled from a traffic light.

Let’s create a chatroom (mediator) through which the participants can register themselves. The chatroom is responsible for handling the routing when the participants chat with each other. 

// each participant represented by Participant object
class Participant {
constructor(name) {
this.name = name;
}
getParticiantDetails() {
return this.name;
}
}
// Mediator
class Chatroom {
constructor() {
this.participants = {};
}
register(participant) {
this.participants[participant.name] = participant;
participant.chatroom = this;
}
send(message, from, to) {
if (to) {
// single message
to.receive(message, from);
} else {
// broadcast message to everyone
for (key in this.participants) {
if (this.participants[key] !== from) {
this.participants[key].receive(message, from);
}
}
}
}
}
// usage
// Create two participants
const john = new Participant('John');
const snow = new Participant('Snow');
// Register the participants to Chatroom
var chatroom = new Chatroom();
chatroom.register(john);
chatroom.register(snow);
// Participants now chat with each other
john.send('Hey, Snow!');
john.send('Are you there?');
snow.send('Hey man', yoko);
snow.send('Yes, I heard that!');
view raw chatroom.js hosted with ❤ by GitHub

5. Command

In the command pattern, an operation is wrapped as a command object and passed to the invoker object. The invoker object passes the command to the corresponding object, which executes the command.

The command pattern decouples the objects executing the commands from objects issuing the commands. The command pattern encapsulates actions as objects. It maintains a stack of commands whenever a command is executed, and pushed to stack. To undo a command, it will pop the action from stack and perform reverse action.

You can consider a calculator as a command that performs addition, subtraction, division and multiplication, and each operation is encapsulated by a command object.

// The list of operations can be performed
const addNumbers = (num1, num2) => num1 + num2;
const subNumbers = (num1, num2) => num1 - num2;
const multiplyNumbers = (num1, num2) => num1 * num2;
const divideNumbers = (num1, num2) => num1 / num2;
// CalculatorCommand class initialize with execute function, undo function // and the value
class CalculatorCommand {
constructor(execute, undo, value) {
this.execute = execute;
this.undo = undo;
this.value = value;
}
}
// Here we are creating the command objects
const DoAddition = value => new CalculatorCommand(addNumbers, subNumbers, value);
const DoSubtraction = value => new CalculatorCommand(subNumbers, addNumbers, value);
const DoMultiplication = value => new CalculatorCommand(multiplyNumbers, divideNumbers, value);
const DoDivision = value => new CalculatorCommand(divideNumbers, multiplyNumbers, value);
// AdvancedCalculator which maintains the list of commands to execute and // undo the executed command
class AdvancedCalculator {
constructor() {
this.current = 0;
this.commands = [];
}
execute(command) {
this.current = command.execute(this.current, command.value);
this.commands.push(command);
}
undo() {
let command = this.commands.pop();
this.current = command.undo(this.current, command.value);
}
getCurrentValue() {
return this.current;
}
}
// usage
const advCal = new AdvancedCalculator();
// invoke commands
advCal.execute(new DoAddition(50)); //50
advCal.execute(new DoSubtraction(25)); //25
advCal.execute(new DoMultiplication(4)); //100
advCal.execute(new DoDivision(2)); //50
// undo commands
advCal.undo();
advCal.getCurrentValue(); //100

6. Facade

The facade pattern is used when we want to show the higher level of abstraction and hide the complexity behind the large codebase.

A great example of this pattern is used in the common DOM manipulation libraries like jQuery, which simplifies the selection and events adding mechanism of the elements.

// JavaScript:
/* handle click event */
document.getElementById('counter').addEventListener('click', () => {
counter++;
});
// jQuery:
/* handle click event */
$('#counter').on('click', () => {
counter++;
});

Though it seems simple on the surface, there is an entire complex logic implemented when performing the operation.

The following Account Creation example gives you clarity about the facade pattern: 

// Here AccountManager is responsible to create new account of type
// Savings or Current with the unique account number
let currentAccountNumber = 0;
class AccountManager {
createAccount(type, details) {
const accountNumber = AccountManager.getUniqueAccountNumber();
let account;
if (type === 'current') {
account = new CurrentAccount();
} else {
account = new SavingsAccount();
}
return account.addAccount({ accountNumber, details });
}
static getUniqueAccountNumber() {
return ++currentAccountNumber;
}
}
// class Accounts maintains the list of all accounts created
class Accounts {
constructor() {
this.accounts = [];
}
addAccount(account) {
this.accounts.push(account);
return this.successMessage(complaint);
}
getAccount(accountNumber) {
return this.accounts.find(account => account.accountNumber === accountNumber);
}
successMessage(account) {}
}
// CurrentAccounts extends the implementation of Accounts for providing more specific success messages on successful account creation
class CurrentAccounts extends Accounts {
constructor() {
super();
if (CurrentAccounts.exists) {
return CurrentAccounts.instance;
}
CurrentAccounts.instance = this;
CurrentAccounts.exists = true;
return this;
}
successMessage({ accountNumber, details }) {
return `Current Account created with ${details}. ${accountNumber} is your account number.`;
}
}
// Same here, SavingsAccount extends the implementation of Accounts for providing more specific success messages on successful account creation
class SavingsAccount extends Accounts {
constructor() {
super();
if (SavingsAccount.exists) {
return SavingsAccount.instance;
}
SavingsAccount.instance = this;
SavingsAccount.exists = true;
return this;
}
successMessage({ accountNumber, details }) {
return `Savings Account created with ${details}. ${accountNumber} is your account number.`;
}
}
// usage
// Here we are hiding the complexities of creating account
const accountManager = new AccountManager();
const currentAccount = accountManager.createAccount('current', { name: 'John Snow', address: 'pune' });
const savingsAccount = accountManager.createAccount('savings', { name: 'Petter Kim', address: 'mumbai' });

7. Adapter

The adapter pattern converts the interface of a class to another expected interface, making two incompatible interfaces work together. 

With the adapter pattern, you might need to show the data from a 3rd party library with the bar chart representation, but the data formats of the 3rd party library API and the display bar chart are different. Below, you’ll find an adapter that converts the 3rd party library API response to Highcharts’ bar representation:

// API Response
[{
symbol: 'SIC DIVISION',
exchange: 'Agricultural services',
volume: 42232,
}]
// Required format
[{
category: 'Agricultural services',
name: 'SIC DIVISION',
y: 42232,
}]
const mapping = {
symbol: 'category',
exchange: 'name',
volume: 'y',
};
const highchartsAdapter = (response, mapping) => {
return response.map(item => {
const normalized = {};
// Normalize each response's item key, according to the mapping
Object.keys(item).forEach(key => (normalized[mapping[key]] = item[key]));
return normalized;
});
};
highchartsAdapter(response, mapping);

Conclusion

This has been a brief introduction to the design patterns in modern JavaScript (ES6). This subject is massive, but hopefully this article has shown you the benefits of using it when writing code.

Related Articles

1. Cleaner, Efficient Code with Hooks and Functional Programming

2. Building a Progressive Web Application in React [With Live Code Examples]

Get the latest engineering blogs delivered straight to your inbox.
No spam. Only expert insights.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Did you like the blog? If yes, we're sure you'll also like to work with the people who write them - our best-in-class engineering team.

We're looking for talented developers who are passionate about new emerging technologies. If that's you, get in touch with us.

Explore current openings

The 7 Most Useful Design Patterns in ES6 (and how you can implement them)

After spending a couple of years in JavaScript development, I’ve realized how incredibly important design patterns are, in modern JavaScript (ES6). And I'd love to share my experience and knowledge on the subject, hoping you’d make this a critical part of your development process as well.

Note: All the examples covered in this post are implemented with ES6 features, but you can also integrate the design patterns with ES5.

At Velotio, we always follow best practices to achieve highly maintainable and more robust code. And we are strong believers of using design patterns as one of the best ways to write clean code. 

In the post below, I’ve listed the most useful design patterns I’ve implemented so far and how you can implement them too:

1. Module

The module pattern simply allows you to keep units of code cleanly separated and organized. 

Modules promote encapsulation, which means the variables and functions are kept private inside the module body and can't be overwritten.

Creating a module in ES6 is quite simple.

// Addition module
export const sum = (num1, num2) => num1 + num2;
view raw modules.js hosted with ❤ by GitHub

// usage
import { sum } from 'modules/sum';
const result = sum(20, 30); // 50
view raw app.js hosted with ❤ by GitHub

ES6 also allows us to export the module as default. The following example gives you a better understanding of this.

// All the variables and functions which are not exported are private within the module and cannot be used outside. Only the exported members are public and can be used by importing them.
// Here the businessList is private member to city module
const businessList = new WeakMap();
// Here City uses the businessList member as it’s in same module
class City {
constructor() {
businessList.set(this, ['Pizza Hut', 'Dominos', 'Street Pizza']);
}
// public method to access the private ‘businessList’
getBusinessList() {
return businessList.get(this);
}
// public method to add business to ‘businessList’
addBusiness(business) {
businessList.get(this).push(business);
}
}
// export the City class as default module
export default City;
view raw city.js hosted with ❤ by GitHub

// usage
import City from 'modules/city';
const city = new City();
city.getBusinessList();
view raw CityExample.js hosted with ❤ by GitHub

There is a great article written on the features of ES6 modules here.

2. Factory

Imagine creating a Notification Management application where your application currently only allows for a notification through Email, so most of the code lives inside the EmailNotification class. And now there is a new requirement for PushNotifications. So, to implement the PushNotifications, you have to do a lot of work as your application is mostly coupled with the EmailNotification. You will repeat the same thing for future implementations.

To solve this complexity, we will delegate the object creation to another object called factory.

class PushNotification {
constructor(sendTo, message) {
this.sendTo = sendTo;
this.message = message;
}
}
class EmailNotification {
constructor(sendTo, cc, emailContent) {
this.sendTo = sendTo;
this.cc = cc;
this.emailContent = emailContent;
}
}
// Notification Factory
class NotificationFactory {
createNotification(type, props) {
switch (type) {
case 'email':
return new EmailNotification(props.sendTo, props.cc, props.emailContent);
case 'push':
return new PushNotification(props.sendTo, props.message);
}
}
}
// usage
const factory = new NotificationFactory();
// create email notification
const emailNotification = factory.createNotification('email', {
sendTo: 'receiver@domain.com',
cc: 'test@domain.com',
emailContent: 'This is the email content to be delivered.!',
});
// create push notification
const pushNotification = factory.createNotification('push', {
sendTo: 'receiver-device-id',
message: 'The push notification message',
});

3. Observer

(Also known as the publish/subscribe pattern.)

An observer pattern maintains the list of subscribers so that whenever an event occurs, it will notify them. An observer can also remove the subscriber if the subscriber no longer wishes to be notified.

On YouTube, many times, the channels we’re subscribed to will notify us whenever a new video is uploaded.

// Publisher
class Video {
constructor(observable, name, content) {
this.observable = observable;
this.name = name;
this.content = content;
// publish the ‘video-uploaded’ event
this.observable.publish('video-uploaded', {
name,
content,
});
}
}
// Subscriber
class User {
constructor(observable) {
this.observable = observable;
this.intrestedVideos = [];
// subscribe with the event naame and the call back function
this.observable.subscribe('video-uploaded', this.addVideo.bind(this));
}
addVideo(video) {
this.intrestedVideos.push(video);
}
}
// Observer
class Observable {
constructor() {
this.handlers = [];
}
subscribe(event, handler) {
this.handlers[event] = this.handlers[event] || [];
this.handlers[event].push(handler);
}
publish(event, eventData) {
const eventHandlers = this.handlers[event];
if (eventHandlers) {
for (var i = 0, l = eventHandlers.length; i < l; ++i) {
eventHandlers[i].call({}, eventData);
}
}
}
}
// usage
const observable = new Observable();
const user = new User(observable);
const video = new Video(observable, 'ES6 Design Patterns', videoFile);
view raw pub-sub.js hosted with ❤ by GitHub

4. Mediator

The mediator pattern provides a unified interface through which different components of an application can communicate with each other.

If a system appears to have too many direct relationships between components, it may be time to have a central point of control that components communicate through instead. 

The mediator promotes loose coupling. 

A real-time analogy could be a traffic light signal that handles which vehicles can go and stop, as all the communications are controlled from a traffic light.

Let’s create a chatroom (mediator) through which the participants can register themselves. The chatroom is responsible for handling the routing when the participants chat with each other. 

// each participant represented by Participant object
class Participant {
constructor(name) {
this.name = name;
}
getParticiantDetails() {
return this.name;
}
}
// Mediator
class Chatroom {
constructor() {
this.participants = {};
}
register(participant) {
this.participants[participant.name] = participant;
participant.chatroom = this;
}
send(message, from, to) {
if (to) {
// single message
to.receive(message, from);
} else {
// broadcast message to everyone
for (key in this.participants) {
if (this.participants[key] !== from) {
this.participants[key].receive(message, from);
}
}
}
}
}
// usage
// Create two participants
const john = new Participant('John');
const snow = new Participant('Snow');
// Register the participants to Chatroom
var chatroom = new Chatroom();
chatroom.register(john);
chatroom.register(snow);
// Participants now chat with each other
john.send('Hey, Snow!');
john.send('Are you there?');
snow.send('Hey man', yoko);
snow.send('Yes, I heard that!');
view raw chatroom.js hosted with ❤ by GitHub

5. Command

In the command pattern, an operation is wrapped as a command object and passed to the invoker object. The invoker object passes the command to the corresponding object, which executes the command.

The command pattern decouples the objects executing the commands from objects issuing the commands. The command pattern encapsulates actions as objects. It maintains a stack of commands whenever a command is executed, and pushed to stack. To undo a command, it will pop the action from stack and perform reverse action.

You can consider a calculator as a command that performs addition, subtraction, division and multiplication, and each operation is encapsulated by a command object.

// The list of operations can be performed
const addNumbers = (num1, num2) => num1 + num2;
const subNumbers = (num1, num2) => num1 - num2;
const multiplyNumbers = (num1, num2) => num1 * num2;
const divideNumbers = (num1, num2) => num1 / num2;
// CalculatorCommand class initialize with execute function, undo function // and the value
class CalculatorCommand {
constructor(execute, undo, value) {
this.execute = execute;
this.undo = undo;
this.value = value;
}
}
// Here we are creating the command objects
const DoAddition = value => new CalculatorCommand(addNumbers, subNumbers, value);
const DoSubtraction = value => new CalculatorCommand(subNumbers, addNumbers, value);
const DoMultiplication = value => new CalculatorCommand(multiplyNumbers, divideNumbers, value);
const DoDivision = value => new CalculatorCommand(divideNumbers, multiplyNumbers, value);
// AdvancedCalculator which maintains the list of commands to execute and // undo the executed command
class AdvancedCalculator {
constructor() {
this.current = 0;
this.commands = [];
}
execute(command) {
this.current = command.execute(this.current, command.value);
this.commands.push(command);
}
undo() {
let command = this.commands.pop();
this.current = command.undo(this.current, command.value);
}
getCurrentValue() {
return this.current;
}
}
// usage
const advCal = new AdvancedCalculator();
// invoke commands
advCal.execute(new DoAddition(50)); //50
advCal.execute(new DoSubtraction(25)); //25
advCal.execute(new DoMultiplication(4)); //100
advCal.execute(new DoDivision(2)); //50
// undo commands
advCal.undo();
advCal.getCurrentValue(); //100

6. Facade

The facade pattern is used when we want to show the higher level of abstraction and hide the complexity behind the large codebase.

A great example of this pattern is used in the common DOM manipulation libraries like jQuery, which simplifies the selection and events adding mechanism of the elements.

// JavaScript:
/* handle click event */
document.getElementById('counter').addEventListener('click', () => {
counter++;
});
// jQuery:
/* handle click event */
$('#counter').on('click', () => {
counter++;
});

Though it seems simple on the surface, there is an entire complex logic implemented when performing the operation.

The following Account Creation example gives you clarity about the facade pattern: 

// Here AccountManager is responsible to create new account of type
// Savings or Current with the unique account number
let currentAccountNumber = 0;
class AccountManager {
createAccount(type, details) {
const accountNumber = AccountManager.getUniqueAccountNumber();
let account;
if (type === 'current') {
account = new CurrentAccount();
} else {
account = new SavingsAccount();
}
return account.addAccount({ accountNumber, details });
}
static getUniqueAccountNumber() {
return ++currentAccountNumber;
}
}
// class Accounts maintains the list of all accounts created
class Accounts {
constructor() {
this.accounts = [];
}
addAccount(account) {
this.accounts.push(account);
return this.successMessage(complaint);
}
getAccount(accountNumber) {
return this.accounts.find(account => account.accountNumber === accountNumber);
}
successMessage(account) {}
}
// CurrentAccounts extends the implementation of Accounts for providing more specific success messages on successful account creation
class CurrentAccounts extends Accounts {
constructor() {
super();
if (CurrentAccounts.exists) {
return CurrentAccounts.instance;
}
CurrentAccounts.instance = this;
CurrentAccounts.exists = true;
return this;
}
successMessage({ accountNumber, details }) {
return `Current Account created with ${details}. ${accountNumber} is your account number.`;
}
}
// Same here, SavingsAccount extends the implementation of Accounts for providing more specific success messages on successful account creation
class SavingsAccount extends Accounts {
constructor() {
super();
if (SavingsAccount.exists) {
return SavingsAccount.instance;
}
SavingsAccount.instance = this;
SavingsAccount.exists = true;
return this;
}
successMessage({ accountNumber, details }) {
return `Savings Account created with ${details}. ${accountNumber} is your account number.`;
}
}
// usage
// Here we are hiding the complexities of creating account
const accountManager = new AccountManager();
const currentAccount = accountManager.createAccount('current', { name: 'John Snow', address: 'pune' });
const savingsAccount = accountManager.createAccount('savings', { name: 'Petter Kim', address: 'mumbai' });

7. Adapter

The adapter pattern converts the interface of a class to another expected interface, making two incompatible interfaces work together. 

With the adapter pattern, you might need to show the data from a 3rd party library with the bar chart representation, but the data formats of the 3rd party library API and the display bar chart are different. Below, you’ll find an adapter that converts the 3rd party library API response to Highcharts’ bar representation:

// API Response
[{
symbol: 'SIC DIVISION',
exchange: 'Agricultural services',
volume: 42232,
}]
// Required format
[{
category: 'Agricultural services',
name: 'SIC DIVISION',
y: 42232,
}]
const mapping = {
symbol: 'category',
exchange: 'name',
volume: 'y',
};
const highchartsAdapter = (response, mapping) => {
return response.map(item => {
const normalized = {};
// Normalize each response's item key, according to the mapping
Object.keys(item).forEach(key => (normalized[mapping[key]] = item[key]));
return normalized;
});
};
highchartsAdapter(response, mapping);

Conclusion

This has been a brief introduction to the design patterns in modern JavaScript (ES6). This subject is massive, but hopefully this article has shown you the benefits of using it when writing code.

Related Articles

1. Cleaner, Efficient Code with Hooks and Functional Programming

2. Building a Progressive Web Application in React [With Live Code Examples]

Did you like the blog? If yes, we're sure you'll also like to work with the people who write them - our best-in-class engineering team.

We're looking for talented developers who are passionate about new emerging technologies. If that's you, get in touch with us.

Explore current openings