Padrões de Projeto em TypeScript: Guia Prático
Padrões de projeto (design patterns) são soluções comprovadas para problemas recorrentes no desenvolvimento de software. Em TypeScript, graças à tipagem forte e aos recursos de POO, muitos padrões são implementados de forma especialmente elegante e segura. Neste artigo, vamos analisar quatro padrões-chave com exemplos reais de código que ajudarão você a escrever um código mais flexível e sustentável.
1. Singleton (Instância Única)
O padrão Singleton garante que uma classe tenha apenas uma instância e fornece um ponto de acesso global a ela. Em TypeScript, isso é especialmente útil para gerenciar a configuração da aplicação ou conexões com o banco de dados.
Implementação usando construtor privado e método estático:
class DatabaseConnection { private static instance: DatabaseConnection; private connection: string;
private constructor() { this.connection = 'PostgreSQL connected'; }
public static getInstance(): DatabaseConnection { if (!DatabaseConnection.instance) { DatabaseConnection.instance = new DatabaseConnection(); } return DatabaseConnection.instance; }
public query(sql: string): void { console.log(`Executing query: ${sql} on ${this.connection}`); }}
// Usoconst db1 = DatabaseConnection.getInstance();const db2 = DatabaseConnection.getInstance();console.log(db1 === db2); // true - mesmo objetodb1.query('SELECT * FROM users');Quando usar: para loggers, caches, pools de threads, configurações da aplicação. Evite Singleton para estados que podem mudar em ambiente multithread.
2. Factory Method (Método Fábrica)
O Factory Method define uma interface para criar um objeto, mas permite que as subclasses alterem o tipo do objeto criado. Em TypeScript, isso combina perfeitamente com interfaces e classes abstratas.
Exemplo de criação de diferentes tipos de notificações:
interface Notification { send(message: string): void;}
class EmailNotification implements Notification { public send(message: string): void { console.log(`Email sent: ${message}`); }}
class SMSNotification implements Notification { public send(message: string): void { console.log(`SMS sent: ${message}`); }}
abstract class NotificationFactory { public abstract createNotification(): Notification;
public notifyUser(message: string): void { const notification = this.createNotification(); notification.send(message); }}
class EmailNotificationFactory extends NotificationFactory { public createNotification(): Notification { return new EmailNotification(); }}
class SMSNotificationFactory extends NotificationFactory { public createNotification(): Notification { return new SMSNotification(); }}
// Usoconst factory: NotificationFactory = new EmailNotificationFactory();factory.notifyUser('Your order has been shipped!');Quando usar: quando não se sabe antecipadamente qual classe concreta será necessária, ou quando a lógica de criação do objeto é complexa e deve ser centralizada.
3. Observer (Observador)
O Observer define uma relação "um para muitos" entre objetos: quando o estado de um objeto muda, todos os objetos dependentes são notificados automaticamente. Em TypeScript, isso é frequentemente implementado com genéricos para tipagem de eventos.
Exemplo de sistema de assinaturas de notícias:
interface Observer<T> { update(data: T): void;}
class NewsPublisher<T> { private observers: Observer<T>[] = [];
public subscribe(observer: Observer<T>): void { this.observers.push(observer); }
public unsubscribe(observer: Observer<T>): void { this.observers = this.observers.filter(obs => o