Most Common Design Pattern for Typescript
We all use design patterns in our code. Sometimes, it's unnecessary, but it could give a nice and understandable structure to your architecture. Since TypeScript is getting more popular, I decided to show some of the popular patterns with its implementation.
Singleton
Singleton is most of the known patterns in the programming world. Basically, you use this pattern if you need to instantiate a restricted number of instances. You do this by making a private constructor and providing a static method, which returns an instance of the class.
Usage
This pattern is used in other patterns. Abstract factory and builder uses it in itself implementations. Sometimes, you use singletons with facades, because you want to provide only one instance of a Facade.
class Singleton {
private static instance: Singleton | null;
private constructor() {}
static getInstance() {
if (!this.instance) {
this.instance = new Singleton();
}
return this.instance;
}
}
Example
import { BehaviorSubject } from 'rxjs';
interface Action {
type: string;
}
class ActionsBus {
private static instance: ActionsBus;
private actionsSubject = new BehaviorSubject<Action>(null);
get actions$() {
return this.actionsSubject.asObservable();
}
private constructor() {
}
static getInstance(): ActionsBus {
if (!ActionsBus.instance) {
ActionsBus.instance = new ActionsBus();
}
return ActionsBus.instance;
}
dispatch(action: Action) {
this.actionsSubject.next(action);
}
}
- constructor with a private access modifier, so that it isn’t accessible outside of the class body,
- static instance filed which is supposed to reference the single instance of the class,
- static getInstance method which is responsible for returning the instance of the class. In addition, it follows a lazy evaluation strategy, hence it has to create the instance when it’s called for the first time.
//illegal since the constructor is private
const illegalActionsBus = new ActionsBus();
const firstActionsBus = ActionsBus.getInstance();
const secondActionsBus = ActionsBus.getInstance();
//both constants reference the same object
console.log(firstActionsBus === secondActionsBus);
Factory and Abstract Factory pattern
Abstract factory is a specific pattern, which used to create an abstract object with an abstract factory. That basically means, that you can put every factory that implements the Abstract Factory and it would return an instance, that implements the Abstract Object interface.
Usage You define two interfaces: an Abstract Factory’s one and a Subject’s one. Then, you implement whatever you want and expose the interface. A client doesn’t know what is inside, he just gets an object with implement methods of an interface.
export interface AbstractProduct {
method(param?: any): void;
}
export class ConcreteProductA implements AbstractProduct {
method = (param?: any) => {
return "Method of ConcreteProductA";
}
}
export class ConcreteProductB implements AbstractProduct {
method = (param?: any) => {
return "Method of ConcreteProductB";
}
}
export namespace ProductFactory {
export function createProduct(type: string): AbstractProduct {
if (type === "A") {
return new ConcreteProductA();
} else if (type === "B") {
return new ConcreteProductB();
}
return null;
}
}
var a: AbstractProduct = ProductFactory.createProduct("A");
var b: AbstractProduct = ProductFactory.createProduct("B");
console.log(a.method());
console.log(b.method());
Abstract Factory Pattern
interface SoundFactory {
create: Function;
}
interface Sound {
enable: Function;
}
class FerrariSound implements Sound {
enable() {
console.log('Wrooom-wrooom-wrooooom!');
}
}
class BirdSound implements Sound {
enable() {
console.log('Flap-flap-flap');
}
}
class FerrariSoundFactory implements SoundFactory {
create() {
return new FerrariSound();
}
}
class BirdSoundFactory implements SoundFactory {
create() {
return new BirdSound();
}
}
(() => {
let factory: SoundFactory | null = null ;
const type = Math.random() > 0.5 ? 'ferrari' : 'bird';
switch (type) {
case 'ferrari':
factory = new FerrariSoundFactory();
break;
case 'bird':
factory = new BirdSoundFactory();
break;
}
if (factory) {
const soundMaker = factory.create();
soundMaker.enable();
}
})();
Observer Pattern
This pattern suggests, that you have a subject and some observers. Every time you update your subject state, observers get notified about it. This pattern is very handy when you need to tie several objects to each other with abstraction and freedom of implementation. Also, this pattern is a key part of the familiar model-view-controller (MVC) architectural pattern. Strongly used in almost every GUI library.
export class Subject {
private observers: Observer[] = [];
public register(observer: Observer): void {
console.log(observer, "is pushed!");
this.observers.push(observer);
}
public unregister(observer: Observer): void {
var n: number = this.observers.indexOf(observer);
console.log(observer, "is removed");
this.observers.splice(n, 1);
}
public notify(): void {
console.log("notify all the observers", this.observers);
var i: number
, max: number;
for (i = 0, max = this.observers.length; i < max; i += 1) {
this.observers[i].notify();
}
}
}
export class ConcreteSubject extends Subject {
private subjectState: number;
get SubjectState(): number {
return this.subjectState;
}
set SubjectState(subjectState: number) {
this.subjectState = subjectState;
}
}
export class Observer {
public notify(): void {
throw new Error("Abstract Method!");
}
}
export class ConcreteObserver extends Observer {
private name: string;
private state: number;
private subject: ConcreteSubject;
constructor(subject: ConcreteSubject, name: string) {
super();
console.log("ConcreteObserver", name, "is created!");
this.subject = subject;
this.name = name;
}
public notify(): void {
console.log("ConcreteObserver's notify method");
console.log(this.name, this.state);
this.state = this.subject.SubjectState;
}
get Subject(): ConcreteSubject {
return this.subject;
}
set Subject(subject: ConcreteSubject) {
this.subject = subject;
}
}
Fluent Interface or chainable Pattern
Often used in testing libraries (e.g., Mocha, Cypress), a fluent interface makes code readable as written prose. It is implemented by using method chaining. Basically, every method returns this. (self) and the chaining ends when a chain method returns void. Also, other techniques used for a fluent interface - nested functions and object scoping.
class Book {
private title: string | undefined;
private author: string | undefined;
private rating: number | undefined;
private content: string | undefined;
setTitle(title: string) {
this.title = title;
return this;
}
setAuthor(author: string) {
this.author = author;
return this;
}
setRating(rating: number) {
this.rating = rating;
return this;
}
setContent(content: string) {
this.content = content;
return this;
}
getInfo() {
return `A ${this.title} book is written by ${this.author} with ${
this.rating
} out of 5 stars`;
}
}
console.log(
new Book()
.setTitle('Voyna i Mir')
.setAuthor('Lev Tolstoy')
.setRating(3)
.setContent('A very long and boring book... Once ago...')
.getInfo(),
);
You can Explore More from Here https://github.com/torokmark/design_patterns_in_typescript
Design Patterns in TypeScript
Here are the implementations of the following design patterns in TypeScript:
Creational
Structural Patterns
Behavioral Patterns
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
Compile the project
$ git clone https://github.com/torokmark/design_patterns_in_typescript.git
$ cd design_patterns_in_typescript
$ tsc
There is a tsconfig.json
file in the root directory which is responsible for the compiler options.
As it is set the default target is Ecmascript5 now.
Any additional options come here.
By default the output is a patterns.js
file.
To compile only one pattern, use the following command.
$ cd design_patterns_in_typescript/visitor
$ tsc --target ES5 --module system --outFile visitor.js visitor.ts
Execute the project
After the compilation of the project, a patterns.js
is generated by default.
Executing the file is:
node patterns.js
Comments