Skip to Content
Course content

156: The Module Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Up until now, we've been writing functions and variables that mostly live in the global scope or are passed around explicitly. But in a real-world project, the global scope is a dangerous place. If you have a variable called score and some third-party library you imported also uses a variable called score, you're going to have a bad time. This is why we use the Module Pattern.

Essentially, we're using closures to create "private" variables that can't be touched from the outside, while selectively exposing a public API. Let's build a GameStats module to handle a player's score and level.

Wrapping our state in a closure

I'll start by using an IIFE (Immediately Invoked Function Expression). This allows me to create a private scope immediately. I want the score and level to be completely inaccessible to anyone who just wants to cheat by typing gameStats.score = 9999 in the browser console.

const GameStats = (function() {
    let score = 0;
    let level = 1;

    // I'll put my logic here
})();

Right now, this is just a function that runs once and returns nothing. The variables score and level exist only while that function is executing, and then they vanish. To make them useful, we need to return an object containing functions that "remember" these variables via closure.

Defining how we actually interact with the score

I don't want the outside world to set the score directly, but I do want them to be able to increase it. I'll add a method to handle the incrementing and another to simply read the value.

const GameStats = (function() {
    let score = 0;
    let level = 1;

    return {
        addPoints: function(points) {
            score += points;
            console.log(`Points added! Current score: ${score}`);
        },
        getScore: function() {
            return score;
        }
    };
})();

Now, if I call GameStats.addPoints(10), it works perfectly. But if I try to console.log(GameStats.score), I get undefined. That's exactly what we want. The data is encapsulated.

Fixing the "undefined" mistake

Here is where I usually trip up when I'm rushing. I wanted to add a feature to track the player's level, so I added a function to increment the level, but I forgot to actually add that function to the returned object. Let's look at my mistake:

const GameStats = (function() {
    let score = 0;
    let level = 1;

    function levelUp() {
        level++;
        console.log(`Leveled up to ${level}!`);
    }

    return {
        addPoints: function(points) {
            score += points;
        },
        getScore: function() {
            return score;
        }
        // I forgot to put levelUp in here!
    };
})();

When I try to call GameStats.levelUp(), JavaScript throws a TypeError because levelUp is private to the IIFE. It exists inside the closure, but it's not part of the public API. To fix this, I have to explicitly map that internal function to a property on the returned object.

const GameStats = (function() {
    let score = 0;
    let level = 1;

    function levelUp() {
        level++;
        console.log(`Leveled up to ${level}!`);
    }

    return {
        addPoints: function(points) {
            score += points;
        },
        getScore: function() {
            return score;
        },
        levelUp: levelUp // Now it's public
    };
})();

Adding a reset mechanism

To wrap this up, I'll add a way to reset the game. This is the beauty of the module pattern: I can provide a controlled way to manipulate the private state without ever exposing the state variables themselves. I'll add a reset method that sets everything back to zero and one.

const GameStats = (function() {
    let score = 0;
    let level = 1;

    function levelUp() {
        level++;
    }

    function reset() {
        score = 0;
        level = 1;
        console.log("Game reset.");
    }

    return {
        addPoints: (pts) => score += pts,
        getScore: () => score,
        levelUp: levelUp,
        reset: reset
    };
})();

Notice I switched to arrow functions for the simpler getters/setters—it keeps the code cleaner. We've now built a secure, self-contained module that protects its own internal state.




📋 Practical Task

Build a Private BankAccount Module

Create a module named BankAccount using the Module Pattern (IIFE). The module should meet the following requirements:

  • Maintain a private variable balance initialized to 0.
  • Expose a deposit method that adds a positive amount to the balance.
  • Expose a withdraw method that subtracts an amount from the balance, but only if the balance is sufficient (otherwise, it should log "Insufficient funds").
  • Expose a getBalance method that returns the current balance.

Ensure that the balance variable cannot be accessed or modified directly from outside the module.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.