Creational design patterns

0

Creational design patterns are a subset of design patterns in software engineering that deal with the process of object creation. They provide solutions to various object instantiation problems and help manage the complexity of object creation. Creational design patterns address how objects are created, composed, and represented.




Here, we'll explore several creational design patterns in-depth.


1. Singleton Pattern:

Intent:

Ensure that a class has only one instance and provide a global point of access to it.


Use Case:

When you need a single, shared resource, such as a configuration manager or a connection pool.


Implementation:

Create a private static instance of the class and provide a static method to access it. Ensure that no other instances can be created.

For Ex.

class Singleton {
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    Singleton.instance = this;
  }
}

// Usage
const singleton1 = new Singleton();
const singleton2 = new Singleton();
console.log(singleton1 === singleton2);  // Output: true


2. Factory Method Pattern:

Intent:

Define an interface for creating an object, but let subclasses alter the type of objects that will be created.


Use Case:

When a class cannot anticipate the class of objects it must create, or when a class wants its subclasses to specify the objects it creates.


Implementation:

Define an interface or abstract class for object creation and let concrete subclasses implement it.

For ex.

class Product {}

class ConcreteProductA extends Product {
  operation() {
    return 'Product A';
  }
}

class ConcreteProductB extends Product {
  operation() {
    return 'Product B';
  }
}

class Creator {
  factoryMethod() {}
}

class ConcreteCreatorA extends Creator {
  factoryMethod() {
    return new ConcreteProductA();
  }
}

class ConcreteCreatorB extends Creator {
  factoryMethod() {
    return new ConcreteProductB();
  }
}

// Usage
const creatorA = new ConcreteCreatorA();
const productA = creatorA.factoryMethod();
console.log(productA.operation());  // Output: Product A


3. Abstract Factory Pattern:

Intent:

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.


Use Case:

When a system needs to be independent of how its objects are created, composed, and represented, or when you want to ensure that families of related objects work together.


Implementation:

Define a set of abstract factories, each responsible for creating a family of related objects. Concrete factories implement these interfaces.

For Ex:

class AbstractProductA {
  operationA() {}
}

class ConcreteProductA1 extends AbstractProductA {
  operationA() {
    return 'Product A1 operation';
  }
}

class ConcreteProductA2 extends AbstractProductA {
  operationA() {
    return 'Product A2 operation';
  }
}

class AbstractProductB {
  operationB() {}
}

class ConcreteProductB1 extends AbstractProductB {
  operationB() {
    return 'Product B1 operation';
  }
}

class ConcreteProductB2 extends AbstractProductB {
  operationB() {
    return 'Product B2 operation';
  }
}

class AbstractFactory {
  createProductA() {}
  createProductB() {}
}

class ConcreteFactory1 extends AbstractFactory {
  createProductA() {
    return new ConcreteProductA1();
  }
  createProductB() {
    return new ConcreteProductB1();
  }
}

class ConcreteFactory2 extends AbstractFactory {
  createProductA() {
    return new ConcreteProductA2();
  }
  createProductB() {
    return new ConcreteProductB2();
  }
}

// Usage
const factory1 = new ConcreteFactory1();
const productA1 = factory1.createProductA();
const productB1 = factory1.createProductB();
console.log(productA1.operationA());  // Output: Product A1 operation
console.log(productB1.operationB());  // Output: Product B1 operation


4. Builder Pattern:

Intent:

Separate the construction of a complex object from its representation, allowing the same construction process to create different representations.


Use Case:

When an object has a large number of possible configurations or when you need to construct an object step by step.


Implementation:

Create a director class that orchestrates the construction process using a builder interface with specific methods for building parts of the object.

For Ex:

// Product to be constructed
class Product {
  constructor() {
    this.parts = [];
  }

  addPart(part) {
    this.parts.push(part);
  }

  show() {
    console.log(`Product Parts: ${this.parts.join(', ')}`);
  }
}

// Builder interface
class Builder {
  buildPart1() {}
  buildPart2() {}
}

// Concrete Builder
class ConcreteBuilder extends Builder {
  constructor() {
    super();
    this.product = new Product();
  }

  buildPart1() {
    this.product.addPart('Part 1');
  }

  buildPart2() {
    this.product.addPart('Part 2');
  }

  getResult() {
    return this.product;
  }
}

// Director
class Director {
  constructor() {
    this.builder = null;
  }

  setBuilder(builder) {
    this.builder = builder;
  }

  construct() {
    this.builder.buildPart1();
    this.builder.buildPart2();
  }
}

// Usage
const director = new Director();
const builder = new ConcreteBuilder();

director.setBuilder(builder);
director.construct();
const product = builder.getResult();

product.show();

In this example:

Product represents the complex object being constructed.

Builder is the abstract builder interface with methods to build various parts.

ConcreteBuilder implements the builder interface and constructs the product.

Director is responsible for orchestrating the construction process.


5. Prototype Pattern:

Intent:

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.


Use Case:

When the cost of creating an object is more expensive than copying an existing object, or when objects have complex initialization that can be shared.


Implementation:

Create a prototype interface with a clone method and concrete classes implement this interface to provide their own cloning logic.

For Ex:

// Prototype object
const prototype = {
  name: 'Default',
  greet() {
    return `Hello, my name is ${this.name}`;
  },
};

// Create new objects by cloning the prototype
const object1 = Object.create(prototype);
object1.name = 'John';

const object2 = Object.create(prototype);
object2.name = 'Alice';

console.log(object1.greet()); // Output: Hello, my name is John
console.log(object2.greet()); // Output: Hello, my name is Alice

In this example:

  1. prototype is the prototype object with default properties and methods.
  2. Object.create(prototype) creates new objects by copying the prototype.
  3. You can customize the properties of each new object as needed.

By using the Prototype Pattern, you can efficiently create new objects based on a shared prototype, reducing the need for resource-intensive object creation and initialization.


6. Lazy Initialization:

Intent:

Delay the creation of an object until the point at which it is needed.


Use Case:

When the creation of an object is resource-intensive and not always needed, or when you want to ensure that an object is created only when it's used.


Implementation:

Create the object when it's first requested and store it for subsequent requests.

For Ex:

class ExpensiveResource {
  constructor() {
    console.log('Creating an expensive resource');
  }

  use() {
    console.log('Using the expensive resource');
  }
}

class LazyResourceProxy {
  constructor() {
    this.resource = null;
  }

  use() {
    if (!this.resource) {
      this.resource = new ExpensiveResource();
    }
    this.resource.use();
  }
}

// Usage
const lazyProxy = new LazyResourceProxy();

// The expensive resource is created only when it is accessed for the first time.
lazyProxy.use();  // Output: Creating an expensive resource, Using the expensive resource
lazyProxy.use();  // Output: Using the expensive resource


These creational design patterns provide solutions to common object creation challenges, helping you manage object instantiation, improve code maintainability, and ensure the flexibility and scalability of your software systems. Each pattern addresses different aspects of object creation, and choosing the appropriate pattern depends on the specific requirements and constraints of your application.



Post a Comment

0Comments
Post a Comment (0)