Creational Design Patterns In TypeScript

Kinanee Samson - Jan 2 '23 - - Dev Community

Design patterns are some general guidelines and principles that have been developed to provide solution to common software development problems especially when working with Object Oriented Programming, these design patterns are not some method laid out in stone so it's not good trying to memorize them, rather you want to train yourself to be able to identify the problems that give rise to the use of these patterns.

These design patterns were introduced by four C++ developers popularly known as the gang of four in 1994, they are Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides, they published a book titled "Design Patterns: Elements of Reusable Object-Oriented Software".

All the design patterns can be categorically divided into three;

  • Creational
  • Structural
  • Behavioral

Creational design patterns are concered with the means by which objects are created in our code. Structural design patterns is concnered with how we organize the classes and objects in our code to form our application structure. Behavioral patterns is concerned with how the Objects can interact with each other in a loosely coupled manner. Note that we will not be addressing all the design patterns under each category, rather we will be addressing those that are applicable to develpment with Typescript, you should know that these design patterns were developed for low level languages that lack some of the features that modern higher level languages posses, thus some of the original design patterns will just add unneccessary layer of complexity to our code rather than helping us out. Let's start with the creational design pattern.

To read more articles like this visit Netcreed

Creational Design Patterns

Singleton Pattern

This pattern involves only creating a single instance of an object, normally once we have one instance of the class you'd disable creation of other instances of the same class, it is often used when a stricter control over global variables is required. We can create a singleton by making the constructor to be private to prevent creating new objects, then we'd implement a getInstance method that returns the current available instance of that class, or if there isn't one, it creates and returns to us an instance.

class Settings {
  private static instance: Settings

  private constructor() {}

  public static getInstance(): Settings {
    if(!Settings.instance) {
      Settings.instance = new Settings()
    }

    return Settings.instance;
  }
}
Enter fullscreen mode Exit fullscreen mode

Builder method

This design pattern allows us to create a complex object in a series of steps, this allows to get a slightly different object each time we are creating an object because we can just omit some of the steps required, thus producing a unique object each time.

Say we want to build a system that creates a burger, now we know we have different types of burger, ham burger, cheese burger, veggie burger, e.t.c.

class Burger {
  public bun: boolean| undefined
  public tomato: boolean| undefined
  public cheese: boolean| undefined
  public veggie: boolean| undefined
  public ham: boolean| undefined


  constructor(private type: string) {}

  addBun() {
    this.bun = true
    return this
  }

  addTomato() {
    this.tomato = true;
    return this
  }

  addCheese() {
    this.cheese = true;
    return this;
  }

  addVeggies() {
    this.veggie = true;
    return this;
  }

  addHam() {
    this.ham = true;
    return this;
  }
}
Enter fullscreen mode Exit fullscreen mode

Observe how we return this every single time we call a builder method, this is known as chaining, because we return this from every function call, we can make multiple calls to methods on the object.

const CheeseBurger = new Burger('cheese').addBun().addCheese()
const HamBurger = new Burger('ham').addBun().addHam()
const VeggieBurger = new Burger('veggie').addBun().addTomato().addVeggies()

console.log(CheeseBurger)
console.log(HamBurger)
console.log(VeggieBurger)

// Burger { type: 'cheese', bun: true, cheese: true }
// Burger { type: 'ham', bun: true, ham: true }
// Burger { type: 'veggie', bun: true, tomato: true, veggie: true }
Enter fullscreen mode Exit fullscreen mode

Use the Builder pattern when you want your code to be able to create different representations of some class e.g a product, The Builder pattern can be applied when construction of various representations of an object involves similar steps that differ only in the details.

Proptye Method

This method is allows us to express inheritance without following a deeply nested hierachichal structure that is observed in classical inhertance. In prototypes, classes are not used to expressed relationship. Rather relationship is expressed on an object level, by copying values existing on particular object into newly created objects.
This is useful for obects with properties that you want to clone to other objects without specifying a strict relationship.

const Menu = {
  isopen: false,
  open() {
    this.isopen = true
    console.log(`menu is ${this.isopen ? 'open': 'closed'}`)
  },
  close(){
    this.isopen = false
    console.log(`menu is ${this.isopen ? 'open': 'closed'}`)
  }
}

let EditorSettings = Object.assign(Menu, {
  editorWith: 10, 
  fontSize: 14, 
  background: 'red'
})

let ConsoleSettings = Object.assign(Menu, {
  consoleColor: '#fff',
  font: 'consolas',
  fontSize: 10,
})

console.log(EditorSettings.isopen)
EditorSettings.open()
console.log(EditorSettings.isopen)

// false
// menu is opened
// true
Enter fullscreen mode Exit fullscreen mode

Factory Method

The factory method allows us to shift the instantiation of one T class into an abstract class X, the method defined on the abstract class X will be responsible for creating objects that of the same class or is a super class or child class of T. What this allows us to do is to create the objects that share methods or properties, most of which will have their own implementation. It leans towards approaching things from a polymorphic perspective and it is inline with the Dependency Injection principle of SOLID.

interface FormControl<T> {
  onChange: (v: T, cb: (_v: T) => void) => void 

  view: () => string
}

interface InputFactory {
  createTextInput: (value: string) => InputControl<string>
  createEmailInput: (value: string) => InputControl<string>
  createPasswordInput: (value: string) => InputControl<string>
  createNumberInput: (value: number) => InputControl<number>
}

class InputControl<T> implements FormControl<T> {
  constructor(public type: string, public value: T, public name?: string){
  }

  onChange(v: T, cb: (_v: T) => void) {
    cb(v);
   this.value = v
  } 


  view() {
    return this.type;
  }
}
Enter fullscreen mode Exit fullscreen mode

Pay attention to the InputFactory and how we are going to be utilizing it below;

class InputCreator implements InputFactory {

  constructor (private platform: string) {}

  createTextInput(value: string, name?: string)  {
    return new InputControl("text", value, name);
  }
  createEmailInput(value: string, name?: string)  {
    return new InputControl("email", value, name);
  }
  createPasswordInput(value: string, name?: string)  {
    return new InputControl("password", value, name);
  }
  createNumberInput(value: number, name?: string)  {
    return new InputControl("number", value, name);
  }

}

let InputFactory = new InputCreator("web");

const TextInput = InputFactory.createTextInput("", "full_name");
const EmailInput = InputFactory.createEmailInput("", "email");
const PasswordInput = InputFactory.createPasswordInput("", "password");
const NumberInput = InputFactory.createNumberInput(1, "phone_number");

Enter fullscreen mode Exit fullscreen mode

The factory method allows us to decouple a client from a particular class that consumes it, we also use the factory pattern to create objects that are of the same family, this abstract factory method will easy any class that is consuming it of the need to create an object.

Proptotypical inheritance is built into JavaScript so we get this behaviour by default, same thing with signleton pattern. Using Object literals, we can create a single instance of a global object that we can toss around.

To read more articles like this visit Netcreed

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .