Builder e Prototype Pattern: Costruzione Flessibile di Oggetti
Builder e Prototype sono pattern creazionali che affrontano la complessità nella creazione di oggetti. Builder costruisce oggetti complessi passo dopo passo con un'interfaccia fluida, mentre Prototype clona oggetti esistenti invece di crearli da zero.
🎯 Cosa Imparerai
- Builder pattern per oggetti complessi con molti parametri
- Fluent interface e method chaining
- Prototype pattern per cloning efficiente
- Deep vs shallow copy
Builder Pattern
Il Builder pattern separa la costruzione di oggetti complessi dalla loro rappresentazione. Utile quando un oggetto ha molti parametri opzionali e il costruttore diventerebbe troppo complesso.
// ❌ Costruttori multipli = confusione
class Pizza {{ '{' }}
constructor(
public size: string,
public cheese?: boolean,
public pepperoni?: boolean,
public bacon?: boolean,
public mushrooms?: boolean
) {{ '{' }}{{ '}' }}
{{ '}' }}
// Difficile ricordare l'ordine dei parametri
const pizza1 = new Pizza("large", true, false, true, false);
const pizza2 = new Pizza("medium", true, true); // Cosa manca?
class Pizza {{ '{' }}
constructor(
public size: string,
public cheese: boolean = false,
public pepperoni: boolean = false,
public bacon: boolean = false,
public mushrooms: boolean = false
) {{ '{' }}{{ '}' }}
{{ '}' }}
class PizzaBuilder {{ '{' }}
private size: string = "medium";
private cheese: boolean = false;
private pepperoni: boolean = false;
private bacon: boolean = false;
private mushrooms: boolean = false;
setSize(size: string): PizzaBuilder {{ '{' }}
this.size = size;
return this; // Method chaining!
{{ '}' }}
addCheese(): PizzaBuilder {{ '{' }}
this.cheese = true;
return this;
{{ '}' }}
addPepperoni(): PizzaBuilder {{ '{' }}
this.pepperoni = true;
return this;
{{ '}' }}
addBacon(): PizzaBuilder {{ '{' }}
this.bacon = true;
return this;
{{ '}' }}
addMushrooms(): PizzaBuilder {{ '{' }}
this.mushrooms = true;
return this;
{{ '}' }}
build(): Pizza {{ '{' }}
return new Pizza(
this.size,
this.cheese,
this.pepperoni,
this.bacon,
this.mushrooms
);
{{ '}' }}
{{ '}' }}
// Utilizzo: chiaro e leggibile!
const pizza = new PizzaBuilder()
.setSize("large")
.addCheese()
.addPepperoni()
.addMushrooms()
.build();
console.log(pizza);
// Pizza {{ '{' }} size: 'large', cheese: true, pepperoni: true, bacon: false, mushrooms: true {{ '}' }}
Builder con Validazione
Il Builder può validare lo stato prima di creare l'oggetto:
class User {{ '{' }}
constructor(
public username: string,
public email: string,
public age?: number,
public address?: string
) {{ '{' }}{{ '}' }}
{{ '}' }}
class UserBuilder {{ '{' }}
private username?: string;
private email?: string;
private age?: number;
private address?: string;
setUsername(username: string): UserBuilder {{ '{' }}
this.username = username;
return this;
{{ '}' }}
setEmail(email: string): UserBuilder {{ '{' }}
this.email = email;
return this;
{{ '}' }}
setAge(age: number): UserBuilder {{ '{' }}
this.age = age;
return this;
{{ '}' }}
setAddress(address: string): UserBuilder {{ '{' }}
this.address = address;
return this;
{{ '}' }}
build(): User {{ '{' }}
// Validazione prima della creazione
if (!this.username || this.username.length < 3) {{ '{' }}
throw new Error("Username deve essere almeno 3 caratteri");
{{ '}' }}
if (!this.email || !this.email.includes('@')) {{ '{' }}
throw new Error("Email non valida");
{{ '}' }}
if (this.age && this.age < 0) {{ '{' }}
throw new Error("Età non può essere negativa");
{{ '}' }}
return new User(this.username, this.email, this.age, this.address);
{{ '}' }}
{{ '}' }}
// Utilizzo
try {{ '{' }}
const user = new UserBuilder()
.setUsername("alice")
.setEmail("alice@example.com")
.setAge(25)
.build();
console.log(user); // User creato con successo
{{ '}' }} catch (error) {{ '{' }}
console.error(error.message);
{{ '}' }}
// Errore di validazione
const invalidUser = new UserBuilder()
.setUsername("ab") // Troppo corto!
.setEmail("invalid-email")
.build(); // ❌ Throw Error: "Username deve essere almeno 3 caratteri"
Fluent Interface
Il pattern Builder usa fluent interface (method chaining) per API leggibili:
class QueryBuilder {{ '{' }}
private table: string = '';
private fields: string[] = ['*'];
private whereConditions: string[] = [];
private orderByField?: string;
private limitValue?: number;
from(table: string): QueryBuilder {{ '{' }}
this.table = table;
return this;
{{ '}' }}
select(...fields: string[]): QueryBuilder {{ '{' }}
this.fields = fields;
return this;
{{ '}' }}
where(condition: string): QueryBuilder {{ '{' }}
this.whereConditions.push(condition);
return this;
{{ '}' }}
orderBy(field: string): QueryBuilder {{ '{' }}
this.orderByField = field;
return this;
{{ '}' }}
limit(value: number): QueryBuilder {{ '{' }}
this.limitValue = value;
return this;
{{ '}' }}
build(): string {{ '{' }}
let query = `SELECT ${{ '{' }}this.fields.join(', '){{ '}' }} FROM ${{ '{' }}this.table{{ '}' }}`;
if (this.whereConditions.length > 0) {{ '{' }}
query += ` WHERE ${{ '{' }}this.whereConditions.join(' AND '){{ '}' }}`;
{{ '}' }}
if (this.orderByField) {{ '{' }}
query += ` ORDER BY ${{ '{' }}this.orderByField{{ '}' }}`;
{{ '}' }}
if (this.limitValue) {{ '{' }}
query += ` LIMIT ${{ '{' }}this.limitValue{{ '}' }}`;
{{ '}' }}
return query;
{{ '}' }}
{{ '}' }}
// Utilizzo: leggibile come linguaggio naturale
const query = new QueryBuilder()
.select('id', 'name', 'email')
.from('users')
.where('age > 18')
.where('active = true')
.orderBy('name')
.limit(10)
.build();
console.log(query);
// "SELECT id, name, email FROM users WHERE age > 18 AND active = true ORDER BY name LIMIT 10"
Prototype Pattern
Il Prototype pattern crea nuovi oggetti clonando istanze esistenti invece
di usare new. Utile quando la creazione è costosa o complessa.
interface Prototype {{ '{' }}
clone(): this;
{{ '}' }}
class Shape implements Prototype {{ '{' }}
constructor(
public x: number,
public y: number,
public color: string
) {{ '{' }}{{ '}' }}
clone(): this {{ '{' }}
// Shallow copy
return Object.create(this);
{{ '}' }}
{{ '}' }}
class Circle extends Shape {{ '{' }}
constructor(
x: number,
y: number,
color: string,
public radius: number
) {{ '{' }}
super(x, y, color);
{{ '}' }}
clone(): this {{ '{' }}
return new Circle(this.x, this.y, this.color, this.radius) as this;
{{ '}' }}
{{ '}' }}
// Utilizzo
const circle1 = new Circle(10, 20, "red", 5);
const circle2 = circle1.clone();
circle2.x = 30;
circle2.color = "blue";
console.log(circle1); // Circle {{ '{' }} x: 10, y: 20, color: 'red', radius: 5 {{ '}' }}
console.log(circle2); // Circle {{ '{' }} x: 30, y: 20, color: 'blue', radius: 5 {{ '}' }}
Deep Copy vs Shallow Copy
La differenza tra shallow copy (copia superficiale) e deep copy (copia profonda) è critica:
class Address {{ '{' }}
constructor(
public street: string,
public city: string
) {{ '{' }}{{ '}' }}
{{ '}' }}
class Person {{ '{' }}
constructor(
public name: string,
public address: Address
) {{ '{' }}{{ '}' }}
// Shallow copy: copia solo i riferimenti
shallowClone(): Person {{ '{' }}
return new Person(this.name, this.address);
{{ '}' }}
// Deep copy: copia ricorsivamente tutti gli oggetti
deepClone(): Person {{ '{' }}
const addressCopy = new Address(this.address.street, this.address.city);
return new Person(this.name, addressCopy);
{{ '}' }}
{{ '}' }}
const person1 = new Person("Alice", new Address("Via Roma", "Milano"));
// Shallow copy
const person2 = person1.shallowClone();
person2.name = "Bob";
person2.address.city = "Torino"; // ⚠️ Modifica ANCHE person1!
console.log(person1.address.city); // "Torino" ← CONDIVISO!
console.log(person2.address.city); // "Torino"
// Deep copy
const person3 = person1.deepClone();
person3.address.city = "Roma"; // ✅ Non tocca person1
console.log(person1.address.city); // "Torino"
console.log(person3.address.city); // "Roma" ← INDIPENDENTE
Prototype con Registry
Un Prototype Registry mantiene un catalogo di prototipi clonabili:
abstract class Enemy {{ '{' }}
constructor(
public health: number,
public damage: number,
public speed: number
) {{ '{' }}{{ '}' }}
abstract clone(): Enemy;
abstract attack(): void;
{{ '}' }}
class Zombie extends Enemy {{ '{' }}
clone(): Zombie {{ '{' }}
return new Zombie(this.health, this.damage, this.speed);
{{ '}' }}
attack(): void {{ '{' }}
console.log("Zombie attacca!");
{{ '}' }}
{{ '}' }}
class Skeleton extends Enemy {{ '{' }}
clone(): Skeleton {{ '{' }}
return new Skeleton(this.health, this.damage, this.speed);
{{ '}' }}
attack(): void {{ '{' }}
console.log("Skeleton lancia freccia!");
{{ '}' }}
{{ '}' }}
// Registry dei prototipi
class EnemyRegistry {{ '{' }}
private prototypes: Map<string, Enemy> = new Map();
register(type: string, prototype: Enemy): void {{ '{' }}
this.prototypes.set(type, prototype);
{{ '}' }}
create(type: string): Enemy {{ '{' }}
const prototype = this.prototypes.get(type);
if (!prototype) {{ '{' }}
throw new Error(`Prototype ${{ '{' }}type{{ '}' }} non trovato`);
{{ '}' }}
return prototype.clone();
{{ '}' }}
{{ '}' }}
// Setup registry
const registry = new EnemyRegistry();
registry.register("zombie", new Zombie(100, 10, 2));
registry.register("skeleton", new Skeleton(50, 15, 5));
// Creazione rapida di nemici
const enemy1 = registry.create("zombie");
const enemy2 = registry.create("zombie");
const enemy3 = registry.create("skeleton");
enemy1.attack(); // "Zombie attacca!"
enemy3.attack(); // "Skeleton lancia freccia!"
Quando Usare Ogni Pattern
✅ Usa Builder quando:
- L'oggetto ha molti parametri opzionali
- Serve validazione complessa prima della creazione
- Costruzione passo-passo migliora leggibilità
- Vuoi API fluide e method chaining
✅ Usa Prototype quando:
- Creazione oggetti è costosa (DB query, parsing, ecc.)
- Hai bisogno di molte istanze simili con variazioni minime
- Vuoi evitare sottoclassi per ogni configurazione
- Cloning è più efficiente di costruzione
Esempio Pratico: Document Editor
// Prototype per documenti
class Document {{ '{' }}
constructor(
public title: string,
public content: string,
public metadata: {{ '{' }} author: string; date: Date {{ '}' }}
) {{ '{' }}{{ '}' }}
clone(): Document {{ '{' }}
return new Document(
this.title,
this.content,
{{ '{' }} ...this.metadata, date: new Date(this.metadata.date) {{ '}' }} // Deep copy
);
{{ '}' }}
{{ '}' }}
// Builder per configurazione complessa
class DocumentBuilder {{ '{' }}
private title: string = "Untitled";
private content: string = "";
private author: string = "Anonymous";
private date: Date = new Date();
setTitle(title: string): DocumentBuilder {{ '{' }}
this.title = title;
return this;
{{ '}' }}
setContent(content: string): DocumentBuilder {{ '{' }}
this.content = content;
return this;
{{ '}' }}
setAuthor(author: string): DocumentBuilder {{ '{' }}
this.author = author;
return this;
{{ '}' }}
build(): Document {{ '{' }}
return new Document(this.title, this.content, {{ '{' }}
author: this.author,
date: this.date
{{ '}' }});
{{ '}' }}
{{ '}' }}
// Utilizzo combinato
const template = new DocumentBuilder()
.setAuthor("System")
.setContent("Template content...")
.build();
// Clona template per nuovi documenti
const doc1 = template.clone();
doc1.title = "Report Q1";
const doc2 = template.clone();
doc2.title = "Report Q2";
Conclusione
Builder e Prototype semplificano la creazione di oggetti complessi. Builder usa fluent interface per costruzione leggibile passo-passo, mentre Prototype clona istanze esistenti per efficienza. Spesso si usano insieme: Builder per configurazione iniziale, Prototype per creazione rapida di varianti.
🎯 Punti Chiave
- Builder = costruzione flessibile con method chaining
- Fluent interface rende codice leggibile
- Prototype = cloning invece di new
- Deep copy vs shallow copy: attenzione agli oggetti annidati
- Registry mantiene catalogo di prototipi riutilizzabili
Domande frequenti
Qual è la principale differenza tra il Pattern Builder e il Pattern Prototipo?
Il Pattern Builder costruisce oggetti complessi passo dopo passo con un'interfaccia fluida, mentre il Pattern Prototipo clona oggetti esistenti invece di crearli da zero.
In che situazioni è utile utilizzare il Pattern Builder?
Il Pattern Builder è utile quando un oggetto ha molti parametri opzionali e il costruttore diventerebbe troppo complesso.
Qual è la differenza tra una copia superficiale (shallow copy) e una copia profonda (deep copy)?
Una copia superficiale copia solo i riferimenti agli oggetti, mentre una copia profonda crea nuove istanze degli oggetti.
In che modo il Pattern Prototipo può essere utilizzato per creare nuovi oggetti?
Il Pattern Prototipo clona istanze esistenti per creare nuovi oggetti, il che è utile quando la creazione è costosa o complessa.

Commenti
Caricamento commenti...
Accedi per commentare