Popular Design Patterns revisited in JavaScript

Popular Design Patterns revisited in JavaScript

Omotola Shogunle

Published on Sep 5, 2020

9 min read

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article

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

Untitled.png

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

Untitled 1.png

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

ezgif-6-c3a0cf0ad993.gif

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

 
Share this
Proudly part of