복합 및 브리지 패턴
복잡성을 관리하는 두 가지 고급 구조 패턴입니다. 합성물 거래 개별 객체와 구성은 균일하게(트리 구조), 반면 다리 폭발을 피하기 위해 추상화와 구현을 분리합니다. 하위 클래스의 조합론.
🎯 무엇을 배울 것인가
- 복합 패턴: 부분-전체 트리 계층
- 단일 객체와 복합 객체를 균일하게 처리
- 브리지 패턴: 추상화와 구현 분리
- Bridge로 하위클래스 폭발 방지
복합 패턴
Il 복합 패턴 개체를 트리 구조로 구성합니다. 부분-전체 계층을 나타냅니다. 클라이언트가 개별 객체를 처리할 수 있도록 허용 그리고 물체의 구성이 균일하게 이루어집니다.
// Component: interfaccia comune
interface FileSystemItem {{ '{' }}
getName(): string;
getSize(): number;
print(indent: string): void;
{{ '}' }}
// Leaf: elementi senza figli
class File implements FileSystemItem {{ '{' }}
constructor(
private name: string,
private size: number
) {{ '{' }}{{ '}' }}
getName(): string {{ '{' }}
return this.name;
{{ '}' }}
getSize(): number {{ '{' }}
return this.size;
{{ '}' }}
print(indent: string = ""): void {{ '{' }}
console.log(`${{ '{' }}indent{{ '}' }}📄 ${{ '{' }}this.name{{ '}' }} (${{ '{' }}this.size{{ '}' }}KB)`);
{{ '}' }}
{{ '}' }}
// Composite: elementi con figli
class Directory implements FileSystemItem {{ '{' }}
private children: FileSystemItem[] = [];
constructor(private name: string) {{ '{' }}{{ '}' }}
getName(): string {{ '{' }}
return this.name;
{{ '}' }}
add(item: FileSystemItem): void {{ '{' }}
this.children.push(item);
{{ '}' }}
remove(item: FileSystemItem): void {{ '{' }}
const index = this.children.indexOf(item);
if (index !== -1) {{ '{' }}
this.children.splice(index, 1);
{{ '}' }}
{{ '}' }}
getSize(): number {{ '{' }}
// Somma ricorsiva dei figli
return this.children.reduce((total, child) => total + child.getSize(), 0);
{{ '}' }}
print(indent: string = ""): void {{ '{' }}
console.log(`${{ '{' }}indent{{ '}' }}📁 ${{ '{' }}this.name{{ '}' }} (${{ '{' }}this.getSize(){{ '}' }}KB)`);
this.children.forEach(child => child.print(indent + " "));
{{ '}' }}
{{ '}' }}
// Utilizzo: crea struttura ad albero
const root = new Directory("root");
const documents = new Directory("documents");
documents.add(new File("resume.pdf", 120));
documents.add(new File("cover_letter.docx", 45));
const photos = new Directory("photos");
photos.add(new File("vacation.jpg", 2500));
photos.add(new File("family.png", 1800));
const work = new Directory("work");
work.add(new File("project.zip", 15000));
work.add(new File("notes.txt", 5));
documents.add(work); // Directory annidato
root.add(documents);
root.add(photos);
root.add(new File("readme.txt", 2));
// Tratta tutto uniformemente
root.print();
// 📁 root (19472KB)
// 📁 documents (15170KB)
// 📄 resume.pdf (120KB)
// 📄 cover_letter.docx (45KB)
// 📁 work (15005KB)
// 📄 project.zip (15000KB)
// 📄 notes.txt (5KB)
// 📁 photos (4300KB)
// 📄 vacation.jpg (2500KB)
// 📄 family.png (1800KB)
// 📄 readme.txt (2KB)
console.log(`Total size: ${{ '{' }}root.getSize(){{ '}' }}KB`); // 19472KB
연산과 복합
실제 예: 작업이 포함된 메뉴 시스템:
// Component interface
interface MenuComponent {{ '{' }}
getName(): string;
execute(): void;
print(indent: string): void;
{{ '}' }}
// Leaf: azioni singole
class MenuItem implements MenuComponent {{ '{' }}
constructor(
private name: string,
private action: () => void
) {{ '{' }}{{ '}' }}
getName(): string {{ '{' }}
return this.name;
{{ '}' }}
execute(): void {{ '{' }}
console.log(`▶️ Executing: ${{ '{' }}this.name{{ '}' }}`);
this.action();
{{ '}' }}
print(indent: string = ""): void {{ '{' }}
console.log(`${{ '{' }}indent{{ '}' }}• ${{ '{' }}this.name{{ '}' }}`);
{{ '}' }}
{{ '}' }}
// Composite: sottomenu
class Menu implements MenuComponent {{ '{' }}
private items: MenuComponent[] = [];
constructor(private name: string) {{ '{' }}{{ '}' }}
getName(): string {{ '{' }}
return this.name;
{{ '}' }}
add(item: MenuComponent): void {{ '{' }}
this.items.push(item);
{{ '}' }}
execute(): void {{ '{' }}
console.log(`📂 Opening menu: ${{ '{' }}this.name{{ '}' }}`);
this.items.forEach(item => item.execute());
{{ '}' }}
print(indent: string = ""): void {{ '{' }}
console.log(`${{ '{' }}indent{{ '}' }}📂 ${{ '{' }}this.name{{ '}' }}`);
this.items.forEach(item => item.print(indent + " "));
{{ '}' }}
{{ '}' }}
// Costruisci menu gerarchico
const mainMenu = new Menu("Main Menu");
const fileMenu = new Menu("File");
fileMenu.add(new MenuItem("New", () => console.log("Creating new file...")));
fileMenu.add(new MenuItem("Open", () => console.log("Opening file...")));
fileMenu.add(new MenuItem("Save", () => console.log("Saving file...")));
fileMenu.add(new MenuItem("Exit", () => console.log("Exiting...")));
const editMenu = new Menu("Edit");
editMenu.add(new MenuItem("Cut", () => console.log("Cutting...")));
editMenu.add(new MenuItem("Copy", () => console.log("Copying...")));
editMenu.add(new MenuItem("Paste", () => console.log("Pasting...")));
const formatMenu = new Menu("Format");
formatMenu.add(new MenuItem("Bold", () => console.log("Applying bold...")));
formatMenu.add(new MenuItem("Italic", () => console.log("Applying italic...")));
editMenu.add(formatMenu); // Sottomenu annidato
mainMenu.add(fileMenu);
mainMenu.add(editMenu);
// Visualizza struttura
mainMenu.print();
// 📂 Main Menu
// 📂 File
// • New
// • Open
// • Save
// • Exit
// 📂 Edit
// • Cut
// • Copy
// • Paste
// 📂 Format
// • Bold
// • Italic
// Esegui tutti
fileMenu.execute();
// 📂 Opening menu: File
// ▶️ Executing: New
// Creating new file...
// ▶️ Executing: Open
// Opening file...
// ▶️ Executing: Save
// Saving file...
// ▶️ Executing: Exit
// Exiting...
UI 구성요소용 복합
실제 예: 중첩된 UI 구성 요소(예: React/Angular):
// Component interface
interface UIComponent {{ '{' }}
render(): string;
addClass(className: string): void;
{{ '}' }}
// Leaf: elementi base
class Button implements UIComponent {{ '{' }}
private classes: string[] = [];
constructor(private text: string) {{ '{' }}{{ '}' }}
addClass(className: string): void {{ '{' }}
this.classes.push(className);
{{ '}' }}
render(): string {{ '{' }}
const classAttr = this.classes.length > 0 ? ` class="${{ '{' }}this.classes.join(' '){{ '}' }}"` : '';
return `<button${{ '{' }}classAttr{{ '}' }}>${{ '{' }}this.text{{ '}' }}</button>`;
{{ '}' }}
{{ '}' }}
class Input implements UIComponent {{ '{' }}
private classes: string[] = [];
constructor(
private placeholder: string,
private type: string = "text"
) {{ '{' }}{{ '}' }}
addClass(className: string): void {{ '{' }}
this.classes.push(className);
{{ '}' }}
render(): string {{ '{' }}
const classAttr = this.classes.length > 0 ? ` class="${{ '{' }}this.classes.join(' '){{ '}' }}"` : '';
return `<input type="${{ '{' }}this.type{{ '}' }}" placeholder="${{ '{' }}this.placeholder{{ '}' }}"${{ '{' }}classAttr{{ '}' }} />`;
{{ '}' }}
{{ '}' }}
// Composite: container
class Panel implements UIComponent {{ '{' }}
private children: UIComponent[] = [];
private classes: string[] = [];
constructor(private title?: string) {{ '{' }}{{ '}' }}
add(component: UIComponent): void {{ '{' }}
this.children.push(component);
{{ '}' }}
addClass(className: string): void {{ '{' }}
this.classes.push(className);
{{ '}' }}
render(): string {{ '{' }}
const classAttr = this.classes.length > 0 ? ` class="${{ '{' }}this.classes.join(' '){{ '}' }}"` : '';
const titleHtml = this.title ? `<h3>${{ '{' }}this.title{{ '}' }}</h3>` : '';
const childrenHtml = this.children.map(child => child.render()).join('\n ');
return `<div${{ '{' }}classAttr{{ '}' }}>
${{ '{' }}titleHtml{{ '}' }}
${{ '{' }}childrenHtml{{ '}' }}
</div>`;
{{ '}' }}
{{ '}' }}
// Costruisci UI
const form = new Panel("Login Form");
form.addClass("card");
form.addClass("shadow");
const usernameInput = new Input("Enter username");
usernameInput.addClass("form-control");
const passwordInput = new Input("Enter password", "password");
passwordInput.addClass("form-control");
const submitButton = new Button("Login");
submitButton.addClass("btn");
submitButton.addClass("btn-primary");
form.add(usernameInput);
form.add(passwordInput);
form.add(submitButton);
console.log(form.render());
// <div class="card shadow">
// <h3>Login Form</h3>
// <input type="text" placeholder="Enter username" class="form-control" />
// <input type="password" placeholder="Enter password" class="form-control" />
// <button class="btn btn-primary">Login</button>
// </div>
브릿지 패턴
Il 브릿지 패턴 다음과 같이 추상화와 구현을 분리합니다. 두 가지가 독립적으로 달라질 수 있다는 것입니다. 조합 폭발을 해결합니다. 두 가지 차원의 가변성이 있는 경우 하위 클래스입니다.
// Implementation: interfaccia implementazione
interface Renderer {{ '{' }}
renderCircle(radius: number): void;
renderSquare(side: number): void;
{{ '}' }}
// Concrete Implementations
class VectorRenderer implements Renderer {{ '{' }}
renderCircle(radius: number): void {{ '{' }}
console.log(`🎨 Drawing vector circle with radius ${{ '{' }}radius{{ '}' }}`);
{{ '}' }}
renderSquare(side: number): void {{ '{' }}
console.log(`🎨 Drawing vector square with side ${{ '{' }}side{{ '}' }}`);
{{ '}' }}
{{ '}' }}
class RasterRenderer implements Renderer {{ '{' }}
renderCircle(radius: number): void {{ '{' }}
console.log(`🖼️ Drawing raster circle (pixels) with radius ${{ '{' }}radius{{ '}' }}`);
{{ '}' }}
renderSquare(side: number): void {{ '{' }}
console.log(`🖼️ Drawing raster square (pixels) with side ${{ '{' }}side{{ '}' }}`);
{{ '}' }}
{{ '}' }}
// Abstraction: forma astratta
abstract class Shape {{ '{' }}
constructor(protected renderer: Renderer) {{ '{' }}{{ '}' }}
abstract draw(): void;
abstract resize(factor: number): void;
{{ '}' }}
// Refined Abstractions
class Circle extends Shape {{ '{' }}
constructor(renderer: Renderer, private radius: number) {{ '{' }}
super(renderer);
{{ '}' }}
draw(): void {{ '{' }}
this.renderer.renderCircle(this.radius);
{{ '}' }}
resize(factor: number): void {{ '{' }}
this.radius *= factor;
{{ '}' }}
{{ '}' }}
class Square extends Shape {{ '{' }}
constructor(renderer: Renderer, private side: number) {{ '{' }}
super(renderer);
{{ '}' }}
draw(): void {{ '{' }}
this.renderer.renderSquare(this.side);
{{ '}' }}
resize(factor: number): void {{ '{' }}
this.side *= factor;
{{ '}' }}
{{ '}' }}
// Utilizzo: combina astrazioni e implementazioni
const vectorCircle = new Circle(new VectorRenderer(), 5);
vectorCircle.draw(); // 🎨 Drawing vector circle with radius 5
const rasterCircle = new Circle(new RasterRenderer(), 5);
rasterCircle.draw(); // 🖼️ Drawing raster circle (pixels) with radius 5
const vectorSquare = new Square(new VectorRenderer(), 10);
vectorSquare.draw(); // 🎨 Drawing vector square with side 10
const rasterSquare = new Square(new RasterRenderer(), 10);
rasterSquare.draw(); // 🖼️ Drawing raster square (pixels) with side 10
// Senza Bridge: avresti bisogno di VectorCircle, RasterCircle, VectorSquare, RasterSquare
// Con 3 forme e 3 renderer = 9 classi!
// Con Bridge: 3 forme + 3 renderer = 6 classi
브리지: 하위클래스 폭발 방지
Bridge가 해결한 문제: 조합 폭발이 없는 가변성의 두 가지 차원:
// ❌ Approccio con ereditarietà: esplosione di classi
class SmallRedCircle {{ '{' }}{{ '}' }}
class SmallBlueCircle {{ '{' }}{{ '}' }}
class SmallGreenCircle {{ '{' }}{{ '}' }}
class MediumRedCircle {{ '{' }}{{ '}' }}
class MediumBlueCircle {{ '{' }}{{ '}' }}
class MediumGreenCircle {{ '{' }}{{ '}' }}
class LargeRedCircle {{ '{' }}{{ '}' }}
class LargeBlueCircle {{ '{' }}{{ '}' }}
class LargeGreenCircle {{ '{' }}{{ '}' }}
// 3 size × 3 colors = 9 classi solo per Circle!
// Aggiungi Square, Triangle → 27 classi!
// Aggiungi un colore → +9 classi!
// Implementation: colori
interface Color {{ '{' }}
fill(): string;
{{ '}' }}
class Red implements Color {{ '{' }}
fill(): string {{ '{' }} return "red"; {{ '}' }}
{{ '}' }}
class Blue implements Color {{ '{' }}
fill(): string {{ '{' }} return "blue"; {{ '}' }}
{{ '}' }}
class Green implements Color {{ '{' }}
fill(): string {{ '{' }} return "green"; {{ '}' }}
{{ '}' }}
// Abstraction: forme con dimensione
abstract class ColoredShape {{ '{' }}
constructor(protected color: Color) {{ '{' }}{{ '}' }}
abstract draw(): void;
{{ '}' }}
class ColoredCircle extends ColoredShape {{ '{' }}
constructor(color: Color, private size: string) {{ '{' }}
super(color);
{{ '}' }}
draw(): void {{ '{' }}
console.log(`🔴 Drawing ${{ '{' }}this.size{{ '}' }} circle with ${{ '{' }}this.color.fill(){{ '}' }} color`);
{{ '}' }}
{{ '}' }}
class ColoredSquare extends ColoredShape {{ '{' }}
constructor(color: Color, private size: string) {{ '{' }}
super(color);
{{ '}' }}
draw(): void {{ '{' }}
console.log(`🟦 Drawing ${{ '{' }}this.size{{ '}' }} square with ${{ '{' }}this.color.fill(){{ '}' }} color`);
{{ '}' }}
{{ '}' }}
// Utilizzo: combina liberamente
new ColoredCircle(new Red(), "small").draw();
// 🔴 Drawing small circle with red color
new ColoredCircle(new Blue(), "large").draw();
// 🔴 Drawing large circle with blue color
new ColoredSquare(new Green(), "medium").draw();
// 🟦 Drawing medium square with green color
// 2 forme + 3 colori = 5 classi (vs 6 con ereditarietà)
// Aggiungi 1 colore → +1 classe (vs +2)
// Scalabile linearmente!
크로스 플랫폼 UI용 브릿지
실제 예: 다양한 플랫폼에서 작동하는 UI:
// Implementation: piattaforme
interface Platform {{ '{' }}
showDialog(title: string, message: string): void;
showNotification(message: string): void;
{{ '}' }}
class WindowsPlatform implements Platform {{ '{' }}
showDialog(title: string, message: string): void {{ '{' }}
console.log(`🪟 Windows Dialog: [${{ '{' }}title{{ '}' }}] ${{ '{' }}message{{ '}' }}`);
{{ '}' }}
showNotification(message: string): void {{ '{' }}
console.log(`🪟 Windows Notification: ${{ '{' }}message{{ '}' }}`);
{{ '}' }}
{{ '}' }}
class MacOSPlatform implements Platform {{ '{' }}
showDialog(title: string, message: string): void {{ '{' }}
console.log(`🍎 macOS Alert: [${{ '{' }}title{{ '}' }}] ${{ '{' }}message{{ '}' }}`);
{{ '}' }}
showNotification(message: string): void {{ '{' }}
console.log(`🍎 macOS Banner: ${{ '{' }}message{{ '}' }}`);
{{ '}' }}
{{ '}' }}
class LinuxPlatform implements Platform {{ '{' }}
showDialog(title: string, message: string): void {{ '{' }}
console.log(`🐧 Linux Dialog: [${{ '{' }}title{{ '}' }}] ${{ '{' }}message{{ '}' }}`);
{{ '}' }}
showNotification(message: string): void {{ '{' }}
console.log(`🐧 Linux Notify: ${{ '{' }}message{{ '}' }}`);
{{ '}' }}
{{ '}' }}
// Abstraction: applicazione
abstract class Application {{ '{' }}
constructor(protected platform: Platform) {{ '{' }}{{ '}' }}
abstract run(): void;
{{ '}' }}
class MessageApp extends Application {{ '{' }}
run(): void {{ '{' }}
this.platform.showDialog("New Message", "You have 5 new messages");
this.platform.showNotification("Message received!");
{{ '}' }}
{{ '}' }}
class SettingsApp extends Application {{ '{' }}
run(): void {{ '{' }}
this.platform.showDialog("Settings", "Configure your preferences");
{{ '}' }}
{{ '}' }}
// Utilizzo: stessa app su diverse piattaforme
console.log("--- Windows ---");
const windowsMsg = new MessageApp(new WindowsPlatform());
windowsMsg.run();
// 🪟 Windows Dialog: [New Message] You have 5 new messages
// 🪟 Windows Notification: Message received!
console.log("\n--- macOS ---");
const macMsg = new MessageApp(new MacOSPlatform());
macMsg.run();
// 🍎 macOS Alert: [New Message] You have 5 new messages
// 🍎 macOS Banner: Message received!
console.log("\n--- Linux ---");
const linuxSettings = new SettingsApp(new LinuxPlatform());
linuxSettings.run();
// 🐧 Linux Dialog: [Settings] Configure your preferences
각 패턴을 사용하는 경우
✅ 다음과 같은 경우에 Composite를 사용하세요:
- 대표하고 싶으신가요? 부분-전체 계층 (나무)
- 클라이언트는 단일 개체 및 구성을 처리해야 합니다. 균일하게
- 재귀 구조: 파일 시스템, 메뉴, UI 구성 요소
- 작업은 계층 구조를 통해 전파되어야 합니다.
✅ 다음과 같은 경우에 Bridge를 사용하세요.
- 당신은 가변성의 두 가지 차원 독립적인
- 피하고 싶으신가요? 조합 폭발 서브클래스
- 추상화와 구현은 독립적으로 달라야 합니다.
- 크로스 플랫폼 개발, 렌더링 엔진, 데이터베이스 드라이버
복합재와 교량 결합
// BRIDGE: separare document da storage
interface DocumentStorage {{ '{' }}
save(content: string): void;
load(): string;
{{ '}' }}
class LocalFileStorage implements DocumentStorage {{ '{' }}
save(content: string): void {{ '{' }}
console.log(`💾 Saving to local file: ${{ '{' }}content.substring(0, 30){{ '}' }}...`);
{{ '}' }}
load(): string {{ '{' }}
return "Loaded from local file";
{{ '}' }}
{{ '}' }}
class CloudStorage implements DocumentStorage {{ '{' }}
save(content: string): void {{ '{' }}
console.log(`☁️ Uploading to cloud: ${{ '{' }}content.substring(0, 30){{ '}' }}...`);
{{ '}' }}
load(): string {{ '{' }}
return "Loaded from cloud";
{{ '}' }}
{{ '}' }}
// COMPOSITE: struttura documento
interface DocumentElement {{ '{' }}
render(): string;
{{ '}' }}
class Paragraph implements DocumentElement {{ '{' }}
constructor(private text: string) {{ '{' }}{{ '}' }}
render(): string {{ '{' }}
return `<p>${{ '{' }}this.text{{ '}' }}</p>`;
{{ '}' }}
{{ '}' }}
class Heading implements DocumentElement {{ '{' }}
constructor(
private text: string,
private level: number
) {{ '{' }}{{ '}' }}
render(): string {{ '{' }}
return `<h${{ '{' }}this.level{{ '}' }}>${{ '{' }}this.text{{ '}' }}</h${{ '{' }}this.level{{ '}' }}>`;
{{ '}' }}
{{ '}' }}
class Section implements DocumentElement {{ '{' }}
private elements: DocumentElement[] = [];
add(element: DocumentElement): void {{ '{' }}
this.elements.push(element);
{{ '}' }}
render(): string {{ '{' }}
const content = this.elements.map(e => e.render()).join('\n');
return `<section>\n${{ '{' }}content{{ '}' }}\n</section>`;
{{ '}' }}
{{ '}' }}
// Document con Bridge per storage
class Document {{ '{' }}
private root = new Section();
constructor(private storage: DocumentStorage) {{ '{' }}{{ '}' }}
add(element: DocumentElement): void {{ '{' }}
this.root.add(element);
{{ '}' }}
save(): void {{ '{' }}
const content = this.root.render();
this.storage.save(content);
{{ '}' }}
load(): void {{ '{' }}
const content = this.storage.load();
console.log(`📄 Document loaded: ${{ '{' }}content{{ '}' }}`);
{{ '}' }}
{{ '}' }}
// Utilizzo combinato
const localDoc = new Document(new LocalFileStorage());
localDoc.add(new Heading("Introduction", 1));
localDoc.add(new Paragraph("This is the introduction paragraph."));
localDoc.add(new Heading("Details", 2));
localDoc.add(new Paragraph("Here are the details."));
localDoc.save();
// 💾 Saving to local file: <section>
// <h1>Introduction</h1>
// <p>Thi...
const cloudDoc = new Document(new CloudStorage());
cloudDoc.add(new Heading("Cloud Document", 1));
cloudDoc.add(new Paragraph("Stored in the cloud."));
cloudDoc.save();
// ☁️ Uploading to cloud: <section>
// <h1>Cloud Document</h1>
// <p>St...
결론
Composite와 Bridge는 복잡한 구조적 문제를 해결합니다. 합성물 재귀적 트리 구조를 위해 단일 객체와 구성을 통합합니다. 다리 폭발을 피하기 위해 구현과 추상화를 분리 조합론. 확장 가능하고 유지 관리 가능한 아키텍처에 사용하세요.
🎯 핵심 포인트
- 복합 = 트리 계층 구조, 리프와 복합을 동일하게 처리
- 파일 시스템, 메뉴, UI 트리에 Composite 사용
- 브리지 = 추상화와 구현을 분리합니다.
- Bridge는 N×M 하위 클래스를 방지합니다(N+M 클래스가 됨).
- 크로스 플랫폼, 다중 렌더러, 다중 데이터베이스에 Bridge 사용
- 둘 다 상속보다 구성을 선호합니다.







