Javascript
  • intro.
  • 1 - Getting started
  • 2 - Basics
  • 3 - Functions and Scope
  • 4 - Advanced Concepts
  • 5 - JavaScript in the Browser
  • 6 - JavaScript and Browser Storage
  • 7 - Asynchronous JavaScript
  • 8 - Design Patterns
  • 9 - Frameworks Overview
  • 10 - Testing and Debugging
Powered by GitBook
On this page

8 - Design Patterns

Introduction to Design Patterns

Design patterns are solutions to common problems in software design. They help developers create more efficient, maintainable, and scalable code. In JavaScript, design patterns are especially useful for structuring code and managing complexity, particularly in larger applications. This chapter will explore some common design patterns including the Module Pattern, Revealing Module Pattern, Factory Pattern, Singleton Pattern, and Observer Pattern.

The Module Pattern

What is the Module Pattern?

  • Module Pattern: A way to encapsulate code into reusable and private blocks, keeping parts of code hidden from the global scope and providing only the necessary interface.

  • Purpose: Prevents pollution of the global namespace and helps manage dependencies.

Example of the Module Pattern

  • Syntax:

    const Module = (function() {
        // Private variables and functions
        let privateVar = "I am private";
        function privateMethod() {
            console.log("Accessing private method");
        }
        
        // Public API
        return {
            publicMethod: function() {
                console.log("Accessing public method");
                privateMethod();
            },
            getPrivateVar: function() {
                return privateVar;
            }
        };
    })();
    
    Module.publicMethod();  // Output: "Accessing public method", "Accessing private method"
    console.log(Module.getPrivateVar());  // Output: "I am private"

Use Cases of the Module Pattern

  • Namespace Management: Avoids global scope pollution by encapsulating variables and methods.

  • Dependency Management: Helps manage dependencies by only exposing the needed methods or properties.

Revealing Module Pattern

What is the Revealing Module Pattern?

  • Revealing Module Pattern: Similar to the Module Pattern but focuses on explicitly exposing methods and properties that are intended to be public.

  • Purpose: More intuitive as all public methods are defined in a single return statement.

Example of the Revealing Module Pattern

  • Syntax:

    const RevealingModule = (function() {
        let privateVar = "I am also private";
        function privateMethod() {
            console.log("Private Method");
        }
        
        function publicMethod() {
            console.log("Public Method");
            privateMethod();
        }
        
        return {
            publicMethod: publicMethod,
            getPrivateVar: () => privateVar
        };
    })();
    
    RevealingModule.publicMethod();  // Output: "Public Method", "Private Method"
    console.log(RevealingModule.getPrivateVar());  // Output: "I am also private"

Factory Pattern

What is the Factory Pattern?

  • Factory Pattern: A design pattern used to create objects without specifying the exact class of object that will be created. This is particularly useful when the exact types of objects are not known until runtime.

Example of the Factory Pattern

  • Syntax:

    function CarFactory(type) {
        if (type === "sedan") {
            return new Sedan();
        } else if (type === "suv") {
            return new SUV();
        }
    }
    
    function Sedan() {
        this.type = "Sedan";
        this.drive = function() {
            console.log("Driving a sedan");
        };
    }
    
    function SUV() {
        this.type = "SUV";
        this.drive = function() {
            console.log("Driving an SUV");
        };
    }
    
    const myCar = CarFactory("sedan");
    myCar.drive();  // Output: "Driving a sedan"

Use Cases of the Factory Pattern

  • Dynamic Object Creation: When the exact type of object to create is determined at runtime.

  • Managing Complex Object Instantiation: Simplifies object creation when there are multiple options or combinations involved.

Singleton Pattern

What is the Singleton Pattern?

  • Singleton Pattern: Ensures a class has only one instance and provides a global point of access to it.

  • Purpose: Useful for cases where having more than one instance can cause conflicts, such as managing a database connection.

Example of the Singleton Pattern

  • Syntax:

    const Singleton = (function() {
        let instance;
        
        function createInstance() {
            const object = new Object("I am the single instance");
            return object;
        }
        
        return {
            getInstance: function() {
                if (!instance) {
                    instance = createInstance();
                }
                return instance;
            }
        };
    })();
    
    const instance1 = Singleton.getInstance();
    const instance2 = Singleton.getInstance();
    console.log(instance1 === instance2);  // Output: true

Use Cases of the Singleton Pattern

  • Centralized State Management: Useful when one global object is needed to coordinate processes or store global state.

  • Caching: Storing the result of expensive operations to avoid repeating them.

Observer Pattern

What is the Observer Pattern?

  • Observer Pattern: A design pattern in which an object (called the subject) maintains a list of dependents (observers) and notifies them of state changes.

  • Purpose: Useful for implementing distributed event handling, such as a subscription model.

Example of the Observer Pattern

  • Syntax:

    function Subject() {
        this.observers = [];  // Array to hold observer functions
    }
    
    Subject.prototype = {
        subscribe: function(fn) {
            this.observers.push(fn);
        },
        unsubscribe: function(fnToRemove) {
            this.observers = this.observers.filter(fn => fn !== fnToRemove);
        },
        notify: function() {
            this.observers.forEach(fn => fn.call());
        }
    };
    
    const subject = new Subject();
    
    function Observer1() {
        console.log("Observer 1 Notified");
    }
    
    function Observer2() {
        console.log("Observer 2 Notified");
    }
    
    subject.subscribe(Observer1);
    subject.subscribe(Observer2);
    subject.notify();  // Output: "Observer 1 Notified", "Observer 2 Notified"
    
    subject.unsubscribe(Observer1);
    subject.notify();  // Output: "Observer 2 Notified"

Use Cases of the Observer Pattern

  • Event Systems: Useful in event-driven architectures, where multiple subscribers react to state changes.

  • Reactive Programming: Real-time notifications of state changes, useful for UI updates.

Strategy Pattern

What is the Strategy Pattern?

  • Strategy Pattern: Defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern lets the algorithm vary independently from the clients that use it.

Example of the Strategy Pattern

  • Syntax:

    function PaymentContext(paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }
    
    PaymentContext.prototype.pay = function(amount) {
        return this.paymentStrategy.pay(amount);
    };
    
    const CreditCardPayment = {
        pay: function(amount) {
            console.log(`Paid ${amount} using Credit Card`);
        }
    };
    
    const PayPalPayment = {
        pay: function(amount) {
            console.log(`Paid ${amount} using PayPal`);
        }
    };
    
    const payment = new PaymentContext(CreditCardPayment);
    payment.pay(100);  // Output: "Paid 100 using Credit Card"
    
    payment.paymentStrategy = PayPalPayment;
    payment.pay(200);  // Output: "Paid 200 using PayPal"

Use Cases of the Strategy Pattern

  • Algorithm Switching: When you need to switch between different algorithms or behaviors at runtime.

  • Behavior Customization: Customize the behavior of a class without changing its code.

Summary

  • Module Pattern: Encapsulates code into reusable modules, reducing global namespace pollution.

  • Revealing Module Pattern: Explicitly defines what is exposed as public from the module.

  • Factory Pattern: Provides a way to create objects without exposing the instantiation logic to the client.

  • Singleton Pattern: Ensures that only one instance of an object exists and provides a global point of access.

  • Observer Pattern: Useful for maintaining consistency between related objects by notifying them of changes.

  • Strategy Pattern: Allows a family of algorithms to be defined and swapped easily without changing client code.


Next Chapter: JavaScript Frameworks Overview

Previous7 - Asynchronous JavaScriptNext9 - Frameworks Overview

Last updated 6 months ago