Skip to content

Latest commit

 

History

History
176 lines (128 loc) · 5.66 KB

File metadata and controls

176 lines (128 loc) · 5.66 KB

Module Pattern

Overview

The Module Pattern is a structural pattern used to create self-contained pieces of code with private and public components. It's the primary way to achieve encapsulation in classic JavaScript, hiding implementation details and only exposing a public API (Application Programming Interface).


Core Idea

  • Wrap code in a function scope to create private variables and functions
  • Return an object containing only the public API
  • Leverage closures to maintain access to private state

When to Use

  • You need private variables in plain JS without classes
  • To group related functionality (e.g., cart, analytics, feature flags)
  • When building reusable, encapsulated code blocks
  • For organizing code into logical units

When NOT to Use

  • In modern JavaScript where ES6 modules are available
  • For simple, stateless utilities
  • When you need inheritance or complex class hierarchies

Core Rules & Responsibilities

  • Define all private variables and functions inside the module function
  • Return only the public interface from the module
  • Use closures to access private state from public methods
  • Avoid polluting the global scope

Minimal Example

const Cart = (function () {
  // Private variables
  const items = [];

  // Private functions
  function calculateTotal() {
    return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  }

  // Public API
  return {
    addItem: function(item) {
      items.push(item);
    },
    getTotal: function() {
      return calculateTotal();
    },
    getItemCount: function() {
      return items.length;
    }
  };
})();

Real-World Problem

Imagine you are creating a simple shopping cart for an e-commerce site. You need to manage cart items privately while providing methods to add items, calculate totals, and get item counts.


Solution

The Module Pattern solves this by encapsulating the cart's internal state (items array) and providing a clean public API.


Classic IIFE Implementation

const PaymentProcessor = (function() {
    // --- PRIVATE SCOPE ---
    // This variable is "private". It cannot be accessed from outside the module.
    let privateApiKey = 'SECRET_12345_XYZ';
    let transactionCount = 0;

    function logTransaction(amount) {
        // This is a private function.
        transactionCount++;
        console.log(`[Log #${transactionCount}] New transaction for $${amount}`);
    }

    // --- PUBLIC API ---
    // We return an object. Only the properties on this object will be accessible from the outside.
    return {
        processPayment: function(amount) {
            // This public function can access the private variables and functions
            // because of a JavaScript feature called a "closure".
            logTransaction(amount);
            console.log(`Processing payment of $${amount} using a secret key.`);
        },

        getTransactions: function() {
            return `Total transactions processed: ${transactionCount}`;
        }
    };
})(); // The () at the end executes the function immediately.

Modern ES6 Modules

The Module Pattern was so important that its concepts became a native feature in ECMAScript 2015 (ES6). Today, you would achieve the same result using import and export.

paymentProcessor.js (a file acts as a module)

// This variable is private to this file (module).
let transactionCount = 0;

// This function is public because it is exported.
export function processPayment(amount) {
    transactionCount++;
    console.log(`Processing payment of $${amount}. Transaction #${transactionCount}`);
}

// This function is also public.
export function getReport() {
    return `Total transactions so far: ${transactionCount}`;
}

main.js (another file)

import { processPayment, getReport } from './paymentProcessor.js';

processPayment(75);
processPayment(25);
console.log(getReport()); // "Total transactions so far: 2"

// You still cannot access `transactionCount` directly.

When to Use the Module Pattern

  • Encapsulation: Whenever you want to hide internal implementation details and expose a clean, public API.
  • Namespace Management: To prevent your variables and functions from "polluting" the global scope, avoiding naming conflicts with other scripts or libraries.
  • Code Organization: To group related functionality into a single, cohesive unit.

Pros & Cons

  • Pros:

    • Encapsulation: Protects your data's integrity by creating private state.
    • Clean Namespace: Avoids global variable conflicts.
    • Organized Code: Leads to a more structured and maintainable codebase.
    • Reusable Components: Modules are self-contained and can be easily reused across your application.
  • Cons (of the classic IIFE pattern):

    • Verbosity: The IIFE syntax can look a bit strange to newcomers.
    • Inflexible Privacy: Once a variable is private, there's no way to access it from the outside, which can sometimes make testing more difficult.
    • Superseded by ES6: Native ES6 modules are now the standard and preferred way to write modular JavaScript. However, understanding the original pattern is crucial for understanding closures and older codebases.

Common Pitfalls

  • Forgetting to return the public API object
  • Accidentally exposing private variables in the return object
  • Trying to access private members from outside the module
  • Using the pattern when ES6 modules would be simpler

Rule of Thumb

Use the Module Pattern when you need encapsulation and private state in environments without native module support. Prefer ES6 modules for modern JavaScript applications.