Why Use Design Patterns?
Design patterns are not specific algorithms or pieces of code. They are reusable solutions to commonly occurring problems in software design. Using them provides:
- Structure: They provide a proven way to organize your code.
- Reusability: Code built with patterns is often easier to reuse and refactor.
- Communication: Developers can use pattern names (e.g., "Let's use a Singleton for that") as a shorthand to communicate complex ideas quickly.
The Module Pattern
The goal of the Module Pattern is to create private and public encapsulation. It helps you avoid polluting the global namespace and keeps parts of your code from interfering with other parts. It uses an IIFE (Immediately Invoked Function Expression) and closures.
- Private: Variables and functions defined inside the module are not accessible from the outside.
- Public: The module can choose to return an object, making some of its members available to the outside world.
JavaScript
const CounterModule = (function() {
  // PRIVATE members
  let count = 0; // 'count' is private
  function increment() {
    count++;
  }
  function getCount() {
    return count;
  }
  // PUBLIC API (what we return)
  return {
    // We only expose 'increment' and 'getCount'
    incrementCounter: function() {
      increment();
      console.log('Counter is now:', getCount());
    },
    currentValue: function() {
      return getCount();
    }
  };
})(); // The () here executes the function immediately
CounterModule.incrementCounter(); // "Counter is now: 1"
CounterModule.incrementCounter(); // "Counter is now: 2"
console.log(CounterModule.currentValue()); // 2
console.log(CounterModule.count); // undefined -> 'count' is private and cannot be accessed!
The Revealing Module Pattern
This is a slight variation of the Module Pattern that makes the public API clearer. All functions are defined privately, and at the end, you "reveal" which ones you want to make public in the return statement.
JavaScript
const RevealingCounterModule = (function() {
  let count = 0;
  // All members are defined privately first
  function privateIncrement() {
    count++;
  }
  function privateGetCount() {
    return count;
  }
  
  // At the end, we "reveal" the public pointers to our private functions
  return {
    increment: privateIncrement,
    value: privateGetCount
  };
})();
RevealingCounterModule.increment();
console.log(RevealingCounterModule.value()); // 1
This pattern makes it very easy to see at a glance what the module's public interface is.
The Factory Pattern
A Factory is a function that creates objects for you. You use it when you need to create different types of objects based on some condition, without the calling code needing to know the specific details of how each object is constructed.
Imagine you're building a game with different types of employees:
JavaScript
function EmployeeFactory() {
  this.create = function(type) {
    let employee;
    if (type === 'developer') {
      employee = new Developer();
    } else if (type === 'tester') {
      employee = new Tester();
    }
    
    employee.type = type;
    employee.say = function() {
      console.log(`Hi, I am a ${this.type} and my rate is ${this.hourly}/hour.`);
    };
    return employee;
  };
}
function Developer() {
  this.hourly = '$45';
}
function Tester() {
  this.hourly = '$35';
}
// --- Usage ---
const factory = new EmployeeFactory();
const employees = [];
employees.push(factory.create('developer'));
employees.push(factory.create('tester'));
employees.forEach(emp => {
  emp.say();
});
// "Hi, I am a developer and my rate is $45/hour."
// "Hi, I am a tester and my rate is $35/hour."
The code that creates employees only needs to know about the factory, not the Developer or Tester "classes".
The Singleton Pattern
A Singleton is a pattern that ensures a class has only one instance and provides a single, global point of access to it. It's useful for managing shared state or resources, like a configuration manager or a database connection pool.
JavaScript
const AppConfig = (function() {
  let instance; // Private instance store
  function createInstance() {
    // Private object with config data
    const config = {
      apiUrl: 'https://api.myapp.com',
      apiKey: 'xyz123'
    };
    return config;
  }
  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();
// --- Usage ---
const config1 = AppConfig.getInstance();
const config2 = AppConfig.getInstance();
console.log(config1.apiKey); // "xyz123"
console.log(config1 === config2); // true -> They are the exact same instance!
No matter how many times you call getInstance(), you will always get back the very same object.