Creational & Structural Patterns
Creational & Structural Patterns Singleton Ensure a class has only one instance and provide a global access point. // Module-level singleton (TypeScript/Node.js…
Creational & Structural Patterns
Singleton
Ensure a class has only one instance and provide a global access point.
// Module-level singleton (TypeScript/Node.js — common pattern)
class DatabaseConnection {
private static instance: DatabaseConnection | null = null;
private constructor(private url: string) {}
static getInstance(): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection(process.env.DB_URL!);
}
return DatabaseConnection.instance;
}
query(sql: string) { /* ... */ }
}
const db = DatabaseConnection.getInstance();Factory Method
Define an interface for creating objects, but let subclasses decide which class to instantiate.
interface Logger {
log(message: string): void;
}
class ConsoleLogger implements Logger {
log(msg: string) { console.log(msg); }
}
class FileLogger implements Logger {
log(msg: string) { fs.appendFileSync('app.log', msg + '
'); }
}
function createLogger(type: 'console' | 'file'): Logger {
const loggers = { console: ConsoleLogger, file: FileLogger };
return new loggers[type]();
}
const logger = createLogger('file');
logger.log('Hello');Builder
Construct complex objects step by step, allowing different representations.
class QueryBuilder {
private table = '';
private conditions: string[] = [];
private columns = '*';
private limitVal?: number;
from(table: string) { this.table = table; return this; }
select(...cols: string[]) { this.columns = cols.join(', '); return this; }
where(condition: string) { this.conditions.push(condition); return this; }
limit(n: number) { this.limitVal = n; return this; }
build(): string {
let q = `SELECT ${this.columns} FROM ${this.table}`;
if (this.conditions.length) q += ` WHERE ${this.conditions.join(' AND ')}`;
if (this.limitVal) q += ` LIMIT ${this.limitVal}`;
return q;
}
}
const query = new QueryBuilder()
.from('users')
.select('id', 'name', 'email')
.where('active = true')
.where('role = "admin"')
.limit(10)
.build();Adapter
Allow incompatible interfaces to work together by wrapping one in an adapter.
// Legacy payment processor
class StripeV1 {
chargeCard(cardNumber: string, amount: number): void { /* ... */ }
}
// New interface
interface PaymentProcessor {
processPayment(amount: number, card: { number: string }): void;
}
// Adapter wraps legacy code to match new interface
class StripeAdapter implements PaymentProcessor {
constructor(private stripe: StripeV1) {}
processPayment(amount: number, card: { number: string }) {
this.stripe.chargeCard(card.number, amount);
}
}
const processor: PaymentProcessor = new StripeAdapter(new StripeV1());
processor.processPayment(100, { number: '4242...' });Decorator
Attach additional behaviors to objects dynamically by wrapping them.
interface DataSource {
read(): string;
write(data: string): void;
}
class FileDataSource implements DataSource {
constructor(private filename: string) {}
read() { return fs.readFileSync(this.filename, 'utf-8'); }
write(data: string) { fs.writeFileSync(this.filename, data); }
}
// Decorator adds compression transparently
class CompressionDecorator implements DataSource {
constructor(private wrapped: DataSource) {}
read() { return decompress(this.wrapped.read()); }
write(data: string) { this.wrapped.write(compress(data)); }
}
class EncryptionDecorator implements DataSource {
constructor(private wrapped: DataSource) {}
read() { return decrypt(this.wrapped.read()); }
write(data: string) { this.wrapped.write(encrypt(data)); }
}
// Stack decorators
const source = new EncryptionDecorator(
new CompressionDecorator(
new FileDataSource('data.bin')
)
);
source.write('sensitive data');