Design Patterns TypeScript

repository·main·Indexed 23 days ago

https://github.com/refactoringguru/design-patterns-typescript

A collection of TypeScript implementations for all classic Gang of Four (GoF) design patterns. The repository provides both conceptual structural examples and real-world application scenarios, including implementations of the Abstract Factory, Adapter, and Bridge patterns.

Tokens
46.5K
Snippets
158
Records
216
Agent score
80%

What's inside design-patterns-typescript

  1. Conceptual output of the Command pattern

    main

    The Command pattern implementation demonstrates the interaction between an Invoker, SimpleCommand, ComplexCommand, and a Receiver.

    • The Invoker triggers the execution and manages the lifecycle (before and after execution).
    • SimpleCommand handles basic, direct actions (e.g., printing).
    • ComplexCommand delegates heavy lifting to a Receiver object.
    • The Receiver contains the actual business logic for complex tasks like sending emails or saving reports.
    Invoker: Does anybody want something done before I begin?
    SimpleCommand: See, I can do simple things like printing (Say Hi!)
    Invoker: ...doing something really important...
    Invoker: Does anybody want something done after I finish?
    ComplexCommand: Complex stuff should be done by a receiver object.
    Receiver: Working on (Send email.)
    Receiver: Also working on (Save report.)
  2. Run a design pattern example using ts-node

    main

    Once the requirements are installed, you can launch any pattern example by pointing ts-node to the specific TypeScript file path. Each pattern typically contains both Conceptual examples (focusing on internal structure) and RealWorld examples (focusing on web application usage).

    ts-node src/Path-to-example/Example.ts
  3. Install requirements to run design pattern examples

    main

    The examples in this repository are console applications. To run them, you must have Node.js and NPM installed. You also need to install the TypeScript compiler and the ts-node extension globally to execute the .ts files directly from the command line.

    npm install -g typescript
    npm install -g ts-node
  4. What is the Proxy design pattern?

    main

    The Proxy pattern provides a surrogate or placeholder for another object to control access to it or to add additional responsibilities.

    Common use cases include:

    • Lazy loading: Delaying the initialization of a resource-intensive object.
    • Caching: Storing results of expensive operations.
    • Access control: Verifying permissions before delegating to the real object.
    • Logging: Recording activity related to the object's methods.
  5. How the Mediator design pattern works

    main

    The Mediator pattern reduces chaotic dependencies between objects by restricting direct communication between them. Instead of components calling each other directly, they communicate only through a central Mediator object.

    Key Abstractions:

    • Mediator Interface: Declares a notify method used by components to signal events to the mediator.
    • Concrete Mediator: Implements the coordination logic. It receives notifications from components and decides which other components should react.
    • Base Component: A base class that provides a way for components to store and access a reference to the Mediator via setMediator().
    • Concrete Components: Implement specific business logic. They do not depend on other components; they only notify the mediator when something happens.
    // Example of the interaction flow:
    const c1 = new Component1();
    const c2 = new Component2();
    const mediator = new ConcreteMediator(c1, c2);
    
    // Component 1 triggers an event, Mediator coordinates response
    c1.doA(); 
  6. Use the State pattern to manage document lifecycle

    main

    The State pattern allows an object (the Document) to alter its behavior when its internal state changes. Instead of using large conditional statements to check the current status, the Document delegates state-specific behavior to a State object.

    In this implementation:

    • Document acts as the context. It maintains a reference to a State instance and provides methods like render() and publish() that call the corresponding methods on the current state.
    • State is the interface defining the behavior allowed in different states.
    • Concrete states (DraftState, ModerationState, PublishedState) implement the interface and define how the document transitions to the next state via document.changeState().
  7. Implement the Singleton design pattern

    main

    The Singleton pattern ensures that a class has only one instance and provides a global access point to it.

    To implement this in TypeScript:

    1. Make the constructor private to prevent direct instantiation using the new operator.
    2. Use a static property (often private) to hold the unique instance.
    3. Provide a public static getter (e.g., instance) that returns the unique instance. If the instance doesn't exist yet, the getter should create it (lazy initialization).
    4. Define business logic methods that can be called on the singleton instance.
    class Singleton {
        static #instance: Singleton;
    
        private constructor() { }
    
        public static get instance(): Singleton {
            if (!Singleton.#instance) {
                Singleton.#instance = new Singleton();
            }
    
            return Singleton.#instance;
        }
    
        public someBusinessLogic() {
            // ...
        }
    }
    
    // Usage
    const s1 = Singleton.instance;
    const s2 = Singleton.instance;
    
    if (s1 === s2) {
        console.log('Singleton works, both variables contain the same instance.');
    }
  8. How the Bridge pattern works for list item views

    main

    The Bridge pattern is used here to decouple two independent dimensions: Views (the abstraction) and Content Types (the implementation).

    1. The Abstraction (ListItemViewAbstraction): Defines how a list item should be structured (e.g., a VisualListItemView or a DescriptiveListItemView). It holds a reference to a content type.
    2. The Implementation (ContentTypeImplementation): Defines the interface for the actual data being displayed (e.g., PostContentType, VideoContentType, or TweetContentType). It provides methods to render specific parts like the title, caption, thumbnail, or link.

    This separation allows you to add new ways to view content (new Abstractions) or new types of content (new Implementations) without modifying existing code.

  9. Implement the Template Method pattern with DataMiner

    main

    The DataMiner abstract class defines a template method mine(path: string) that outlines a fixed sequence of steps for a data mining process:

    1. openFile(path)
    2. extractData()
    3. parseData()
    4. analyzeData()
    5. sendReport()
    6. closeFile()

    To use this pattern, extend DataMiner and implement the required abstract methods. You can optionally override the default implementations of analyzeData() and sendReport() to customize the behavior.

    abstract class DataMiner {
      mine(path: string): void {
        this.openFile(path);
        this.extractData();
        this.parseData();
        this.analyzeData();
        this.sendReport();
        this.closeFile();
      }
    
      abstract openFile(path: string): void;
      abstract extractData(): void;
      abstract closeFile(): void;
      abstract parseData(): void;
    
      analyzeData() {
        console.log("Analyzing data... (Default implementation)");
      }
    
      sendReport() {
        console.log("Sending report... (Default implementation)");
      }
    }
    
    class DocDataMiner extends DataMiner {
      openFile(path: string) {
        console.log(`Opening DOC file: ${path}`);
      }
    
      extractData() {
        console.log("Extracting data from DOC file");
      }
    
      closeFile() {
        console.log("Closing DOC file");
      }
    
      parseData() {
        console.log("Parsing data from DOC file");
      }
    }
    
    const docDataMiner = new DocDataMiner();
    docDataMiner.mine("data.doc");
  10. How the Flyweight pattern works

    main

    The Flyweight pattern is used to reduce memory usage by sharing common parts of state between multiple objects. It distinguishes between two types of state:

    1. Intrinsic State: The common portion of the state that belongs to multiple entities. This is stored within the Flyweight object.
    2. Extrinsic State: The unique state that varies for each entity. This is not stored in the Flyweight but is passed to its methods as parameters during execution.

    By sharing the intrinsic state, you can fit significantly more objects into available RAM.

  11. Use the accept method on Node objects

    main

    The Node class and its subclasses (City, Industry, SightSeeing) use the accept method to facilitate the Visitor pattern. Instead of the client calling visitor methods directly, the client calls node.accept(visitor). The node then 'dispatches' the call back to the visitor by calling the specific method that matches the node's type (e.g., visitor.doForCity(this)). This allows you to add new operations to the node structure without modifying the node classes themselves.

    const graph: Node[] = [new City(), new Industry(), new SightSeeing()];
    const xmlExportVisitor = new ExportVisitor();
    
    graph.forEach((node) => node.accept(xmlExportVisitor));