Design Patterns in TypeScript: Examples and Implementation

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Design Patterns in TypeScript: From Theory to Practice



Design Patterns are proven solutions to typical problems in software development. In the context of TypeScript, with its powerful type system and OOP support, many classic patterns from the "Gang of Four" (GoF) book receive a new, more elegant implementation. In this article, we will cover three key patterns with real TypeScript examples that will help you write cleaner, more scalable, and maintainable code.



1. Creational Pattern: Singleton



Purpose: Ensures that a class has only one instance and provides a global point of access to it.



In TypeScript, implementing Singleton is especially convenient thanks to static properties and private constructors. This pattern is ideal for managing database connections, loggers, or caches.



class DatabaseConnection {    private static instance: DatabaseConnection;    private connectionString: string;

private constructor(connectionString: string) { this.connectionString = connectionString; console.log(`Connection created to: ${connectionString}`); }

public static getInstance(connectionString?: string): DatabaseConnection { if (!DatabaseConnection.instance) { if (!connectionString) { throw new Error("Connection string required for first initialization"); } DatabaseConnection.instance = new DatabaseConnection(connectionString); } return DatabaseConnection.instance; }

public query(sql: string): void { console.log(`Executing query: ${sql} on ${this.connectionString}`); }}

// Usageconst db1 = DatabaseConnection.getInstance("postgres://localhost:5432/mydb");const db2 = DatabaseConnection.getInstance();

console.log(db1 === db2); // true — same objectdb1.query("SELECT * FROM users");


Important: In TypeScript, you can use readonly and generics for more flexible implementations. However, remember that Singleton can complicate testing — consider dependency injection (DI) as an alternative.



2. Structural Pattern: Decorator



Purpose: Dynamically adds new responsibilities to an object without changing its structure. In TypeScript, decorators are a first-class language feature (experimental in ES5, but actively used in Angular and NestJS).



Consider an example with a decorator for logging method calls:



// Target interfaceinterface ILogger {    log(message: string): void;}

// Base classclass SimpleLogger implements ILogger { public log(message: string): void { console.log(`[LOG]: ${message}`); }}

// Decorator (wrapper class)class TimestampDecorator implements ILogger { private logger: ILogger;

constructor(logger: ILogger) { this.logger = logger; }

public log(message: string): void { const timestamp = new Date().toISOString(); this.logger.log(`[${timestamp}] ${message}`); }}

// Decorator for formattingclass UpperCaseDecorator implements ILogger { private logger: ILogger;

constructor(logger: ILogger) { this.logger = logger; }

public log(message: string): void { this.logger.log(message.toUpperCase()); }}

// Usagelet logger: ILogger = new SimpleLogger();logger.log("Hello world"); // [LOG]: Hello world

logger = new TimestampDecorator(logger);logger = new UpperCaseDecorator(logger);logger.log("decorators work"); // [LOG]: [2025-01-15T10:30:00.000Z] DECORATORS WORK


Tip: In TypeScript, class and property decorators are also available (syntax @decorator

Blogs

Book Recommendations