빌더 및 프로토타입 패턴: 유연한 객체 구성
빌더 e 원기 이는 다음을 다루는 생성 패턴입니다. 객체 생성의 복잡성. Builder는 복잡한 객체를 단계별로 구축합니다. 매끄러운 인터페이스를 갖춘 반면 Prototype은 처음부터 객체를 생성하는 대신 기존 객체를 복제합니다.
🎯 무엇을 배울 것인가
- 매개변수가 많은 복잡한 객체를 위한 패턴 빌더
- 유창한 인터페이스 및 메소드 체이닝
- 효율적인 복제를 위한 프로토타입 패턴
- 깊은 복사본과 얕은 복사본
빌더 패턴
Il 빌더 패턴 복잡한 객체의 구성을 객체의 구성과 분리합니다. 표현. 객체에 선택적 매개변수와 생성자가 많을 때 유용합니다. 너무 복잡해질 것입니다.
// ❌ 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 {{ '}' }}
검증된 빌더
빌더는 객체를 생성하기 전에 상태를 확인할 수 있습니다.
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"
유창한 인터페이스
빌더 패턴은 다음을 사용합니다. 유창한 인터페이스 (메소드 체이닝) 읽기 가능한 API:
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"
프로토타입 패턴
Il 프로토타입 패턴 대신 기존 인스턴스를 복제하여 새 개체를 만듭니다.
사용하다 new. 생성 비용이 많이 들거나 복잡할 때 유용합니다.
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 {{ '}' }}
깊은 복사와 얕은 복사
차이점은 다음과 같습니다. 얕은 사본 (얕은 사본) e 딥 카피 (깊은 복사)가 중요합니다.
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
레지스트리를 사용한 프로토타입
Un 프로토타입 레지스트리 복제 가능한 프로토타입 카탈로그를 유지 관리합니다.
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!"
각 패턴을 사용하는 경우
✅ 다음과 같은 경우 빌더를 사용하세요:
- 객체는 많은 선택적 매개변수
- 생성하기 전에 복잡한 검증이 필요합니다
- 단계별 구성으로 가독성이 향상됩니다.
- 원활한 API 및 메소드 체이닝을 원합니다.
✅ 다음과 같은 경우 프로토타입을 사용하세요.
- 객체를 생성하는 것은 값비싼 (DB 쿼리, 파싱 등)
- 최소한의 변형으로 유사한 인스턴스가 많이 필요합니다.
- 각 구성에 대해 하위 클래스를 피하고 싶습니다.
- 복제는 구축보다 효율적입니다.
실제 예: 문서 편집기
// 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";
결론
Builder와 Prototype을 사용하면 복잡한 개체를 쉽게 만들 수 있습니다. 빌더 읽기 쉬운 단계별 구성을 위해 유창한 인터페이스를 사용하는 반면 원기 효율성을 위해 기존 인스턴스를 복제합니다. 그들은 종종 함께 사용됩니다: 구성을 위한 빌더 초기, 변형의 신속한 생성을 위한 프로토타입입니다.
🎯 핵심 포인트
- Builder = 메소드 체이닝을 통한 유연한 구성
- Fluent 인터페이스를 통해 코드를 읽을 수 있습니다.
- 프로토타입 = 새로운 대신 복제
- 전체 복사본과 얕은 복사본: 중첩된 개체에 주의하세요.
- 등록소는 재사용 가능한 프로토타입 카탈로그를 유지합니다.







