Popular Design Patterns revisited in JavaScript
The Module Pattern
This pattern is used to isolate code from the Global scope. The code inside the isolated container can only be accessed if it is revealed. In ES5 writing the module pattern used something called an IIFE (Immediately Invoked Function Expression) and the syntax for this looked like this.
// Structure of an IIFE
(function (){
//Declare your private variables and functions here
//Used to expose public variables and functions that other modules can use
return {
}
})()
So for example
const UICtrl = (function(){
let stories = [];
function addStory(story){
stories.push(story);
alert("Story added");
}
function getStory(id){
stories.find(story => {
return story.id === id;
})
}
return {
addStory: addStory,
getStory: getStory
}
})();
UICtrl.addStory({id: 1, title: "This is a story"});
UICtrl.getStory(1)
But with the Introduction of ES6 it uses the export and import statement to isolate code in different files. So using ES6 to rewrite the above it would look like this.
//Name of file is called generateStory.js
let stories = [{id: 1, title: "This is a story"},
{id: 2, title: "Intro to programming"},
{id: 3, title: "Why exercise?"}
];
//Return all stories
export const getStories = () => stories;
//Add a story and display the story just added to the user
export const addStory = (story, callback, id) => {
stories.push(story);
callback(id);
alert("Story added");
};
//Get a single story object.
export const getStory = (id) => {
let story = stories.filter(story => story.id === id )
story.length != 0 ? console.log(story[0]) : console.log("Story not found")
}
To import this file in app.js for example first we would have to a add a module type to our app.js file in index.html
<script type="module" src="app.js"></script>
And now to use it in app.js we write
import { getStories, addStory, getStory } from './generateStory.js'
document.addEventListener('DOMContentLoaded', () => {
console.log(getStories());
addStory({id: 4, title: "Who wants to be a millionaire?"}, getStory, 4);
})
Here we have successfully been able to use the functions we exported from generateStory.js file into our app.js file where we have called them when our page is loaded.
- getStories() returns all the stories in our array
addStory() takes in three arguments an object of the story you want to add, a callback function in this case getStory() and an ID that should be the same id that you created the post with.
Notice I used callbacks in this example, its okay to be used like this here but there are other ways this can be written. Check here to see a better way!
The Factory Pattern
One of my favourtites - This pattern can be used to create multiple objects that share similar properties but vary in a few unique properties. In this example we would be using the Factory Pattern to create various membership for different users.
class MembershipFactory{
createMemeber(name,type){
let member = {};
if(type === 'simple') {
member = new SimpleMember(name);
}
else if (type === 'standard'){
member = new StandardMember(name);
}
else if (type === 'premium'){
member = new PremiumMember(name);
}
else {
member = new NullMember(name)
}
member.type = type;
member.define = function(){
console.log(`Member (${this.name}) has a ${this.type} membership that cost ${this.cost}`)
}
return member;
}
}
The main class has a constructor with property name, followed by the function to createMembers that takes in two arguments.
- Depending on the type of membership a different class is created and the name is passed to it.
- We can also add properties to our member variable by using the syntax member.type, or member.define to add a function which displays information about the member
- Notice the this keyword used to display information about the user. This refers to the current object. And in this case it would be assumed when we start creating our objects.
- Finally we return the member.
In the same file, I have created the classes that represent our Membership types. What varies between these classes is the value of the cost. Finally notice the NullMember class we give a default value of null when a type is passed in that is not a valid type.
class SimpleMember {
constructor(name) {
this.name = name;
this.cost = '£15';
}
}
class StandardMember {
constructor(name) {
this.name = name;
this.cost = '£25';
}
}
class PremiumMember {
constructor(name) {
this.name = name;
this.cost = '£35';
}
}
class NullMember {
constructor(name) {
this.name = null
this.cost = null;
}
}
Lets see if it works.
let members = []
let factory = new MembershipFactory();
document.addEventListener('DOMContentLoaded', () => {
members.push(factory.createMemeber('Omotola', 'simple'))
members.push(factory.createMemeber('Larry', 'standard'))
members.push(factory.createMemeber('Jude', 'premium'))
members.push(factory.createMemeber('QBot', 'estient'))
members.forEach(member => member.define());
})
Output
Try console logging just the member, what other properties does it show?
The Observer Pattern
You can get really creative with this. This pattern is used to subscribe to events during the lifecycle of your page being loaded or if the user clicks the button etc. The example implementation I have done for this is something we are familiar with on social media. Subscribing/Unsubscribing to channels. First our UI/Code looks like this
We have got two influencers who we would like to subscribe to. The buttons allows us to subscribe, unsubscribe and finally view our subscription user post. How do we do this?
class EventObserver{
constructor() {
this.observers = []
}
subscribe(fn){
this.observers.push(fn)
console.log("%c You are subscribed", "color: black; font-weight: bold; background-color: green")
}
unsubscribe(fn){
this.observers = this.observers.filter(function(item){
if(item !== fn){
return item;
}
})
console.log("%c Your subscription has been revoke", "color: black; font-weight: bold; background-color: red")
}
fire(){
this.observers.forEach((item) =>{
item.call()
})
}
}
First we have an EventObserver class that has a construtor with an observers variable set to be an array. Inside this class we gave three methods subscribe, unsubscribe and fire.
- Subscribe takes in a function which is pushed to our observer's array.
- Unsubscribe returns only functions that do not match the function that we passed in.
- Some fancy console.log are added to give more understanding.
- Finally the fire function allows the user to view recent post for the their subscriptions. This is done by calling each function in the array using .call()
Now to implementing the functions we actually want to call.
const influencer1 = () => {
const tweets = [{tweet: "This is great, proud moment"}, {tweet: "Love JS"}];
console.log("%c Recent tweets from influencer1", "color: white; font-weight: bold; background-color: black;")
tweets.forEach((post => console.log(post.tweet)))
}
const influencer2 = () => {
const tweets = [{tweet: "Whats the difference between time and effort?"}];
console.log("%c Recent tweets from influencer2", "color: white; font-weight: bold; background-color: black;")
tweets.forEach((post => console.log(post.tweet)))
}
These two functions simply display a list of post by each 'influencer'
Now to use this we write the code like this.
const event = new EventObserver();
//Subscribe
document.querySelector(".if1").addEventListener('click', () => {
event.subscribe(influencer1);
})
document.querySelector(".if2").addEventListener('click', () => {
event.subscribe(influencer2);
})
//Unsubscribe
document.querySelector(".unif1").addEventListener('click', () => {
event.unsubscribe(influencer1);
})
document.querySelector(".unif2").addEventListener('click', () => {
event.unsubscribe(influencer2);
})
//Call all functions in subscription list.
document.querySelector(".fire").addEventListener('click', () => {
event.fire();
})
The result
Alright I dont want to make this too long... So there might be a part two.... depending on the reaction to this post! Is there a pattern you would want me to write about? If this was helpful please
LIKE, SHARE AND LEAVE A COMMENT —- MEANS A LOT
You might find these other post interesting if you have read this far.
How to use Object Oriented Programming in JavaScript
Learning JavaScript? My Dating Analogy for CallBacks / Promises / Async & Awaits
Learning JavaScript? Series - Debugging like a pro using Console.log()
You might want to skip this section...... JUST AN APPRECIATION TEXT - Finally completed the #2articles1week challenge today! It was very challenging and exciting taking it on, special thanks to Victoria Lo, Bolaji Ayodeji and Tapas Adhikary for the feedback and post reshares
Software developer, JavaScript Euthusiasts, Inspiring Blogger
Omotola Shogunle, you are killing each and every article about JavaScript. I love reading your blogpost. I cannot wait for the next one.
I will like to have some clarification about the Factory Design pattern.
I thought that the factory class should be independent and not be inherited by another class. I do not see the point of passing name
to the constructor of MembershipFactory
.
A tip that I can give you for putting a screenshot of VScode in your blog post will be to use Screenshot mockup.
See the picture below, which is now more elegant.
Dinys Monvoisin Thank you! Yes I absolutely see your point and it definitely makes sense to do it that way. When I was researching on this topic the examples I saw used functional programming to make a factory pattern. I thought it would a nice challenge to use classes instead.
Thank you I would make the changes!
If you want to make use of OOP. You could make a parent class call Membership for PremierMemberShip, StandardMembership and so forth. Because you are repeating yourself with the name property.
I am just sharing my opinion about the subject do not take me wrong.
I am here learning too.
Software Engineer, Content Creator, Community Engineer, and Developer Advocate.
You're amazing Omotola Shogunle.
Keep them coming, we love your articles 🎊
☝ UI/UX/ML | ✍️ Technology Blogger | 🎤 Speaker
Congratulation Omotola Shogunle on completing the challenge. This is very inspiring. Your articles are always amazing to read..keep it up!
A nerd in books, tea, games and software.
Another wonderful article! Congrats on completing the #2Aritcles1Week challenge! So proud~~
P.S. The code below 'The Factory Pattern' has createMemeber(name,type){...
, the word Member is mispelled.
Yay that's great to hear Omotola Shogunle ! But don't stress too much abt these small typos. I'll be your 2nd pair of eyes haha~
Developer, Hashnode
This was great. You got everything covered. Bookmarked 🎉
I write daily web development tips that help you become a more efficient developer. 👨💻⚡️
Wow, nice article, really helps me understand these patterns better.
Comments (17)