DEV Community

loading...
Cover image for JS and Design patterns - Chapter 2 🚀

JS and Design patterns - Chapter 2 🚀

codespresso profile image Codespresso Updated on ・4 min read

🤓 INTRODUCTION

Welcome, dear coders! I am really glad that you are interested in this kind of content. Here we are, at the second chapter of the Design Patterns explained using JavaScript programming language series. If you missed the previous introductory chapter of this series, please check it out at the link below!

In this article, we will discuss another design pattern The Singleton Pattern.

🤷‍♂️ WHAT IS THE SINGLETON PATTERN?

Singleton is a creational design pattern that ensures that a class has only one instance.

There can be only one

Usually, the goal is to manage the global application state. To share data between the different parts of an application (components) that are not interconnected. You can find it being used as the source of config settings for a web app, on the client-side for anything initiated with an API key, and to store data in memory in a client-side web application such as Flux, Redux, or Vuex. Also, a singleton can be used as a singleton service (Like Angular does). When using it as a service, you manage that only one service instance exists in an app. Of course, those are all applications within a specific JavaScript library or platform such as Angular, React, or Vue.

❔ Since we're talking about JavaScript and its libraries and platforms, I would really like to know what is your preferred library/platform? (comment out which one and why 😉)

⚒ IMPLEMENTATION

Real-World application using simplified Load Balancer logic

//Simplified load balancer
var loadBalancer = (function() {
    // this is the unique
    // load balancer instance variable
    var loadBalancerInstance;
    // load balancer available servers 
    const servers = [];
    // function that 
    //will create the load balancer (assign servers) ONLY once
    function create() {
        servers.push("SERVER I");
        servers.push("SERVER II");
        servers.push("SERVER III");
        servers.push("SERVER IV");
        return servers;
    }

    //function for getting random server
    function getServer() {
        return Math.floor(Math.random() * loadBalancerInstance) + 1;
    }

    return {
        /*function that 
        will either create or 
        not create a new 
        load balancer instance */
        getInstance: function() {
            //if the load balancer 
            //instance is null or undefined, the load balancer 
            //will be created!
            if (!loadBalancerInstance) {
                loadBalancerInstance = create();
            } //if the load balancer is already created we just return an existing instance

            return loadBalancerInstance;
        }
    };
})();
//trying to create the 
//load balancer instance - Success
var balancer_0 = loadBalancer.getInstance();
//trying to create the
//load balancer instance
// - Error - Instance is already created
//previously created instance is assigned to any  
//of the following variable
//add as many as you want

var balancer_1 = loadBalancer.getInstance();
var balancer_2 = loadBalancer.getInstance();

/*Check if we are not wrong if 
all variables are the 
same instance print 
out fancy console.log with the
appropriate message
if variables are not the same 
instance print out the fancy console.log
with the appropriate message*/

if (balancer_0 === balancer_1 && balancer_0 === balancer_2) {
    console.log('%c%s', 'color: white; background: lightgreen; font-size: 24px;', 'Balancers are the same instance!')
} else {
    console.log('%c%s', 'color: black; background: red; font-size: 24px;', 'Balancers are not the same instance!')
}
Enter fullscreen mode Exit fullscreen mode

💡 The singleton is not freed until the termination of the program

🧐 DISCUSSION

The Singleton design pattern is a very specific type of single instance, specifically one that is:

  • Accessible via a global, static instance field;
  • Created either on program initialization or upon first access;
  • No public constructor (cannot instantiate directly);
  • Never explicitly freed (implicitly freed on program termination).

It is because of this specific design choice that the pattern introduces several potential long-term problems:

  • Inability to use abstract or interface classes;
  • Inability to subclass;
  • High coupling across the application (difficult to modify);
  • Difficult to test (can't fake/mock in unit tests);
  • Difficult to parallelize in the case of mutable state (requires extensive locking);

In JavaScript, Singletons serve as a shared resource namespace that isolates implementation code from the global namespace so as to provide a single point of access for functions.

✅ PROS

  • You can be sure that a class has only a single instance
  • You gain a global access point to that instance
  • The singleton object is initialized only when it's requested for the first time

❌ CONS

  • Violates the [Single Responsibility Principal](https://en.wikipedia.org/wiki/Single-responsibility_principle#:~:text=The%20single%2Dresponsibility%20principle%20(SRP,functionality%2C%20which%20it%20should%20encapsulate.)
  • The Singleton pattern can mask bad design, for instance, when the components of the program know too much about each other
  • The pattern requires special treatment in a multithreaded environment so that multiple threads won't create a singleton object several times
  • It may be difficult to unit test the client code of the Singleton because many test frameworks rely on inheritance when producing mock objects. Since the constructor of the Singleton class is private and overriding static methods is impossible in most languages, you will need to think of a creative way to mock the singleton. Or just don't write the tests. Or don't use the Singleton pattern.

*Whilst the Singleton has valid uses, often when we find ourselves needing it in JavaScript it's a sign that we may need to re-evaluate our design.&

They're often an indication that modules in a system are either tightly coupled or that logic is overly spread across multiple parts of a codebase. Singletons can be more difficult to test due to issues ranging from hidden dependencies, the difficulty in creating multiple instances, difficulty in stubbing dependencies, and so on.

🙏 THANK YOU FOR READING!

Please leave the comment, tell me about you, about your work, comment your thoughts, connect with me via Twitter or LinkedIn.

Let this year be your year, let this year be our year. Until the next typing...

Have a nice time!

References:
robdodson
addyosmani
refactoring
School notes...

☕ SUPPORT ME AND KEEP ME FOCUSED!
Buy Me a Coffee at ko-fi.com
😊

Discussion (3)

pic
Editor guide
Collapse
juanda_dev profile image
Juan Martínez • Edited

I'm sort of new to these topics like design patterns and programming paradigms, but I recently saw a video of a guy managing a cookie library with something like this but in a class. I think it'll be great for most common functions like handling the change of a state in a text field on React. Also, I guess it improves this declarative programming stuff instead of putting all the logic visible in your code, isn't it?

Thanks for sharing this, It's good to learn something new every day!

Collapse
codespresso profile image
Codespresso Author

Thank you for your comment! 🚀 There are certainly ways of how you could use, and when we use design patterns. Cookie library is one of the ways we are using it, also, we could use it to implement a singleton service, that way we can hold a part of an application's state, for example, we could expose an API service to an application, that will allow us to interact with that specific piece of state. Those services can provide, for example, that we don't have to send props down to the child components from the respective parent components, but instead, we are using application state and giving direct access to the state for any component. The state that is governing the text field change is actually a specific Component current state. The state as it is should be treated as immutable. So, manual state mutation may be overridden when setState is processed. Of course, managing the entire application state with store comes with drawbacks, for example, Redux, the reducer updates the state by returning a new state every time, because of immutability. That can cause excessive use of memory. So we should be very careful when deciding to use application state management.

I will certainly talk about this in future blogs, so please stay tuned! 😎

Collapse
juanda_dev profile image
Juan Martínez

Amazing! I never thought about this problem with Redux. I recently learned how to use it in my projects but it looks like it's going to be a problem in the future. I'll be waiting for your posts.