CREATIONAL
Factory Method
Definire un'interfaccia per creare un oggetto, ma lasciare che le sottoclassi decidano quale classe istanziare.
Esempi di Codice
❌ PROBLEMA: Codice Rigido con Classi Concrete
Quando il codice dipende direttamente da classi concrete, diventa difficile estendere.
TYPESCRIPT
// ❌ PROBLEMA: Il codice è rigidamente accoppiato alle classi concrete
class NotificationService {
sendNotification(type: string, message: string) {
// ❌ Switch statement: aggiungere nuovi tipi richiede modificare questo codice
if (type === 'email') {
const email = new EmailNotification();
email.send(message);
} else if (type === 'sms') {
const sms = new SMSNotification();
sms.send(message);
} else if (type === 'push') {
const push = new PushNotification();
push.send(message);
}
// ❌ Per aggiungere Slack, Telegram, etc. dobbiamo modificare QUESTA funzione!
}
}
class EmailNotification {
send(message: string) {
console.log(`📧 Email: ${message}`);
}
}
class SMSNotification {
send(message: string) {
console.log(`📱 SMS: ${message}`);
}
}
class PushNotification {
send(message: string) {
console.log(`🔔 Push: ${message}`);
}
}
// ❌ Utilizzo rigido
const service = new NotificationService();
service.sendNotification('email', 'Order shipped!');
service.sendNotification('sms', 'Code: 123456');
// ❌ Problemi:
// 1. Violazione Open/Closed Principle
// 2. Impossibile estendere senza modificare NotificationService
// 3. Testing difficile (dipendenze hard-coded)✅ SOLUZIONE: Factory Method - Estensibilità
Factory Method permette alle sottoclassi di decidere quale classe istanziare.
TYPESCRIPT
// ✅ SOLUZIONE: Factory Method con estensibilità
// Product interface - tutti i tipi di notifica implementano questa
interface Notification {
send(message: string): void;
}
// Concrete Products - implementazioni specifiche
class EmailNotification implements Notification {
send(message: string): void {
console.log(`📧 Sending email: ${message}`);
// Logica specifica per email (SMTP, HTML formatting, etc.)
}
}
class SMSNotification implements Notification {
send(message: string): void {
console.log(`📱 Sending SMS: ${message}`);
// Logica specifica per SMS (Twilio API, length limit, etc.)
}
}
class PushNotification implements Notification {
send(message: string): void {
console.log(`🔔 Sending push: ${message}`);
// Logica specifica per push (FCM, APNS, etc.)
}
}
// Creator astratto - definisce il factory method
abstract class NotificationFactory {
// Factory Method - da implementare nelle sottoclassi
abstract createNotification(): Notification;
// Template method che usa il factory method
notify(message: string): void {
// ✅ Non sa quale tipo concreto viene creato
const notification = this.createNotification();
notification.send(message);
}
}
// Concrete Creators - decidono quale Product creare
class EmailNotificationFactory extends NotificationFactory {
createNotification(): Notification {
return new EmailNotification();
}
}
class SMSNotificationFactory extends NotificationFactory {
createNotification(): Notification {
return new SMSNotification();
}
}
class PushNotificationFactory extends NotificationFactory {
createNotification(): Notification {
return new PushNotification();
}
}
// ✅ Utilizzo flessibile
const emailFactory = new EmailNotificationFactory();
emailFactory.notify('Your order has shipped!');
const smsFactory = new SMSNotificationFactory();
smsFactory.notify('Verification code: 123456');
// ✅ Aggiungere nuovi tipi è facile - basta creare nuove classi!
class SlackNotification implements Notification {
send(message: string): void {
console.log(`💬 Sending Slack message: ${message}`);
}
}
class SlackNotificationFactory extends NotificationFactory {
createNotification(): Notification {
return new SlackNotification();
}
}
const slackFactory = new SlackNotificationFactory();
slackFactory.notify('Build completed successfully!');
// ✅ Nessuna modifica al codice esistente richiesta!🎯 Esempio Pratico: Sistema di Export Multi-Formato
Export di report in formati diversi (PDF, Excel, CSV) usando Factory Method.
TYPESCRIPT
// 🎯 Esempio reale: Export di report in formati diversi
// Product interface
interface ReportExporter {
export(data: any[]): void;
getFileExtension(): string;
}
// Concrete Products
class PDFExporter implements ReportExporter {
export(data: any[]): void {
console.log('📄 Generating PDF report...');
console.log('- Creating document structure');
console.log('- Adding headers and footers');
console.log('- Formatting tables');
console.log(`- Exporting ${data.length} rows`);
console.log(`✅ PDF saved as report.${this.getFileExtension()}`);
}
getFileExtension(): string {
return 'pdf';
}
}
class ExcelExporter implements ReportExporter {
export(data: any[]): void {
console.log('📊 Generating Excel report...');
console.log('- Creating workbook');
console.log('- Adding worksheets');
console.log('- Applying cell formatting');
console.log(`- Writing ${data.length} rows`);
console.log(`✅ Excel saved as report.${this.getFileExtension()}`);
}
getFileExtension(): string {
return 'xlsx';
}
}
class CSVExporter implements ReportExporter {
export(data: any[]): void {
console.log('📋 Generating CSV report...');
console.log('- Converting to comma-separated values');
console.log('- Escaping special characters');
console.log(`- Writing ${data.length} rows`);
console.log(`✅ CSV saved as report.${this.getFileExtension()}`);
}
getFileExtension(): string {
return 'csv';
}
}
// Creator astratto
abstract class ReportGenerator {
// Factory Method
abstract createExporter(): ReportExporter;
// Business logic che usa il factory method
generateReport(data: any[], filename: string): void {
console.log(`\n🔄 Starting report generation for ${filename}...`);
const exporter = this.createExporter();
const fullFilename = `${filename}.${exporter.getFileExtension()}`;
console.log(`Format: ${exporter.getFileExtension().toUpperCase()}`);
exporter.export(data);
console.log('─'.repeat(50));
}
}
// Concrete Creators
class PDFReportGenerator extends ReportGenerator {
createExporter(): ReportExporter {
return new PDFExporter();
}
}
class ExcelReportGenerator extends ReportGenerator {
createExporter(): ReportExporter {
return new ExcelExporter();
}
}
class CSVReportGenerator extends ReportGenerator {
createExporter(): ReportExporter {
return new CSVExporter();
}
}
// 🎯 Utilizzo in una applicazione reale
const salesData = [
{ month: 'Jan', sales: 10000 },
{ month: 'Feb', sales: 15000 },
{ month: 'Mar', sales: 12000 }
];
// Client code - può scegliere il formato dinamicamente
function exportReport(format: string) {
let generator: ReportGenerator;
switch(format) {
case 'pdf':
generator = new PDFReportGenerator();
break;
case 'excel':
generator = new ExcelReportGenerator();
break;
case 'csv':
generator = new CSVReportGenerator();
break;
default:
throw new Error('Unsupported format');
}
generator.generateReport(salesData, 'sales_report_2024');
}
// Esportazione in formati diversi
exportReport('pdf');
exportReport('excel');
exportReport('csv');🆚 Confronto: Switch vs Factory Method
Differenza tra codice procedurale e pattern Factory Method.
TYPESCRIPT
// 🆚 CONFRONTO DIRETTO
// ❌ APPROCCIO PROCEDURALE (con switch)
class ProceduralLogger {
log(type: string, message: string) {
switch(type) {
case 'file':
console.log(`[FILE] Writing to disk: ${message}`);
break;
case 'console':
console.log(`[CONSOLE] ${message}`);
break;
case 'database':
console.log(`[DB] Inserting log: ${message}`);
break;
// ❌ Per aggiungere 'cloud' devo modificare questa funzione!
}
}
}
const procLogger = new ProceduralLogger();
procLogger.log('file', 'Error occurred');
procLogger.log('console', 'Info message');
// ✅ FACTORY METHOD (estensibile)
interface Logger {
log(message: string): void;
}
class FileLogger implements Logger {
log(message: string): void {
console.log(`[FILE] Writing to disk: ${message}`);
}
}
class ConsoleLogger implements Logger {
log(message: string): void {
console.log(`[CONSOLE] ${message}`);
}
}
class DatabaseLogger implements Logger {
log(message: string): void {
console.log(`[DB] Inserting log: ${message}`);
}
}
abstract class LoggerFactory {
abstract createLogger(): Logger;
writeLog(message: string): void {
const logger = this.createLogger();
logger.log(message);
}
}
class FileLoggerFactory extends LoggerFactory {
createLogger(): Logger {
return new FileLogger();
}
}
class ConsoleLoggerFactory extends LoggerFactory {
createLogger(): Logger {
return new ConsoleLogger();
}
}
// ✅ Aggiungere 'cloud' non richiede modifiche al codice esistente!
class CloudLogger implements Logger {
log(message: string): void {
console.log(`[CLOUD] Uploading log: ${message}`);
}
}
class CloudLoggerFactory extends LoggerFactory {
createLogger(): Logger {
return new CloudLogger();
}
}
const fileFactory = new FileLoggerFactory();
fileFactory.writeLog('Error occurred');
const cloudFactory = new CloudLoggerFactory();
cloudFactory.writeLog('System started'); // Nuovo tipo senza modifiche!Esempi nel Mondo Reale
Framework UI - Creazione di componenti diversi per piattaforme diverse (web, mobile, desktop)
Sistema di logging - Diversi logger per console, file, database, cloud
Parser di documenti - Factory per creare parser XML, JSON, CSV in base al tipo di file
Connessioni database - Factory per creare connessioni MySQL, PostgreSQL, MongoDB
Gestione pagamenti - Factory per creare processori Stripe, PayPal, Apple Pay
Quando Usarlo
Quando non sai in anticipo quali tipi di oggetti dovrai creare
Quando vuoi delegare la logica di creazione alle sottoclassi
Quando vuoi fornire estensibilità ai tuoi utenti/library
Quando vuoi localizzare la conoscenza delle classi concrete
Quando NON Usarlo
Quando hai solo un tipo di oggetto da creare
Quando la gerarchia di classi diventa troppo complessa
Quando Simple Factory Pattern è sufficiente
Pattern Correlati