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 WORKTip: In TypeScript, class and property decorators are also available (syntax @decorator
Blogs
Book Recommendations
Read also
WebSocket on TypeScript: Advanced Development of Real-Time Applications
Deploying a Java Application on VPS: Advanced Level with CI/CD
UserManager.isUserAGoat(): Myths, Memes, and Real-World Application in Android
Monitoring TypeScript Applications: Metrics and Alerts
A/B Testing in C++: How Not to Break Production and Find the Truth