Modern Design Patterns
Chapitre 7
Chapitre 07 — Composite, Facade et Flyweight
Chapitre 07 — Composite, Facade et Flyweight
Cours 07 — Composite, Facade et Flyweight
Partie 1 : Composite
1.1 Définition
Le Composite compose des objets en structures d'arbre pour représenter des hiérarchies partie-tout. Il permet aux clients de traiter les objets individuels et les compositions de manière uniforme.
Diagramme en cours de génération...
1.2 Problème résolu
- Représenter des structures hiérarchiques (arbres)
- Permettre aux clients de traiter uniformément objets simples et composés
- Éviter les checks de type (
if (obj instanceof Leaf))
1.3 Exemple : Système de fichiers
interface FileSystemNode {
getName(): string;
getSize(): number;
display(indent: string): void;
}
class File implements FileSystemNode {
constructor(
private name: string,
private size: number
) {}
getName(): string { return this.name; }
getSize(): number { return this.size; }
display(indent: string): void {
console.log(`${indent}📄 ${this.name} (${this.size} bytes)`);
}
}
class Directory implements FileSystemNode {
private children: FileSystemNode[] = [];
constructor(private name: string) {}
getName(): string { return this.name; }
add(node: FileSystemNode): void {
this.children.push(node);
}
remove(node: FileSystemNode): void {
const index = this.children.indexOf(node);
if (index >= 0) this.children.splice(index, 1);
}
getSize(): number {
return this.children.reduce((sum, child) => sum + child.getSize(), 0);
}
display(indent: string): void {
console.log(`${indent}📁 ${this.name}/ (${this.getSize()} bytes)`);
for (const child of this.children) {
child.display(indent + " ");
}
}
}
// Usage
const root = new Directory("root");
const docs = new Directory("docs");
const src = new Directory("src");
root.add(docs);
root.add(src);
docs.add(new File("readme.md", 100));
docs.add(new File("license.txt", 50));
src.add(new File("index.ts", 500));
src.add(new File("utils.ts", 300));
root.display("");
// 📁 root/ (950 bytes)
// 📁 docs/ (150 bytes)
// 📄 readme.md (100 bytes)
// 📄 license.txt (50 bytes)
// 📁 src/ (800 bytes)
// 📄 index.ts (500 bytes)
// 📄 utils.ts (300 bytes)
1.4 Exemple : UI Tree (DOM)
interface UIComponent {
render(): string;
add(child: UIComponent): void;
remove(child: UIComponent): void;
}
class UIText implements UIComponent {
constructor(private text: string) {}
render(): string {
return this.text;
}
add(child: UIComponent): void { /* leaf */ }
remove(child: UIComponent): void { /* leaf */ }
}
class UIPanel implements UIComponent {
private children: UIComponent[] = [];
constructor(private tag: string, private style: string = "") {}
render(): string {
const childrenHtml = this.children.map(c => c.render()).join("\n");
return `<${this.tag}${this.style ? ` style="${this.style}"` : ""}>\n${childrenHtml}\n</${this.tag}>`;
}
add(child: UIComponent): void {
this.children.push(child);
}
remove(child: UIComponent): void {
const idx = this.children.indexOf(child);
if (idx >= 0) this.children.splice(idx, 1);
}
}
// Usage
const page = new UIPanel("div");
const header = new UIPanel("header", "background: blue;");
header.add(new UIText("Welcome!"));
page.add(header);
const menu = new UIPanel("nav");
menu.add(new UIText("Home"));
menu.add(new UIText("About"));
menu.add(new UIText("Contact"));
page.add(menu);
console.log(page.render());
Partie 2 : Facade
2.1 Définition
La Facade fournit une interface unifiée de plus haut niveau pour un ensemble d'interfaces d'un sous-système, rendant le sous-système plus facile à utiliser.
Diagramme en cours de génération...
2.2 Problème résolu
- Simplifier l'utilisation d'un sous-système complexe
- Réduire le couplage entre le client et le sous-système
- Fournir une API de haut niveau
2.3 Exemple : Bibliothèque de traitement vidéo
// Sous-système complexe
class VideoCodec {
decode(file: string): Buffer {
console.log(`Decoding video: ${file}`);
return Buffer.from("decoded");
}
encode(data: Buffer, format: string): Buffer {
console.log(`Encoding to ${format}`);
return Buffer.from("encoded");
}
}
class AudioProcessor {
extractAudio(video: Buffer): Buffer {
console.log("Extracting audio track");
return Buffer.from("audio");
}
normalize(audio: Buffer): Buffer {
console.log("Normalizing audio levels");
return Buffer.from("normalized");
}
}
class SubtitleParser {
loadSubtitles(file: string): string[] {
console.log(`Loading subtitles: ${file}`);
return ["Subtitle 1", "Subtitle 2"];
}
merge(video: Buffer, subtitles: string[]): Buffer {
console.log("Merging subtitles with video");
return Buffer.from("with_subs");
}
}
class FileExporter {
export(data: Buffer, format: string, output: string): void {
console.log(`Exporting to ${output} in ${format} format`);
}
}
// Facade
class VideoConverter {
private codec = new VideoCodec();
private audio = new AudioProcessor();
private subtitles = new SubtitleParser();
private exporter = new FileExporter();
convert(input: string, output: string, format: string): void {
console.log(`=== Converting ${input} to ${format} ===`);
const decoded = this.codec.decode(input);
const audioData = this.audio.extractAudio(decoded);
this.audio.normalize(audioData);
const subtitles = this.subtitles.loadSubtitles(input);
const withSubs = this.subtitles.merge(decoded, subtitles);
const encoded = this.codec.encode(withSubs, format);
this.exporter.export(encoded, format, output);
console.log("=== Conversion complete ===");
}
}
// Client
const converter = new VideoConverter();
converter.convert("input.mp4", "output.avi", "MPEG-4");
2.4 Exemple : API de librairie
// Sous-systèmes
class AuthService {
login(email: string, password: string): string {
return "token_jwt_123";
}
}
class UserService {
getUser(token: string): any {
return { id: 1, name: "Alice" };
}
}
class CartService {
getCart(userId: number): any[] {
return [{ product: "Widget", qty: 2 }];
}
}
class PaymentService {
checkout(userId: number, items: any[]): string {
return "order_456";
}
}
class NotificationService {
sendConfirmation(email: string, orderId: string): void {
console.log(`Email sent to ${email}: Order ${orderId}`);
}
}
// Facade
class StoreAPI {
private auth = new AuthService();
private users = new UserService();
private cart = new CartService();
private payment = new PaymentService();
private notification = new NotificationService();
buy(email: string, password: string): string {
const token = this.auth.login(email, password);
const user = this.users.getUser(token);
const items = this.cart.getCart(user.id);
const orderId = this.payment.checkout(user.id, items);
this.notification.sendConfirmation(email, orderId);
return orderId;
}
}
// Client : 1 appel au lieu de 5
const store = new StoreAPI();
const order = store.buy("alice@test.com", "password123");
console.log(`Order placed: ${order}`);
Partie 3 : Flyweight
3.1 Définition
Le Flyweight permet de partager efficacement un grand nombre d'objets de granularité fine en mutualisant les données intrinsèques (partagées) et en externalisant les données extrinsèques (contextuelles).
Diagramme en cours de génération...
3.2 Problème résolu
- Optimiser la mémoire quand un grand nombre d'objets partagent des données communes
- Réduire le nombre d'instances en mutualisant les états intrinsèques
3.3 Exemple : Système de polices
class Font {
constructor(
public family: string,
public size: number,
public weight: number, // intrinsic : partagé
public style: string
) {}
}
class Character {
constructor(
public char: string, // extrinsic : contexte
public font: Font // intrinsic : partagé
) {}
render(): string {
return `<span style="font-family:${font.family}; font-size:${font.size}px; font-weight:${font.weight}">${this.char}</span>`;
}
}
// Flyweight Factory
class FontFactory {
private fonts: Map<string, Font> = new Map();
getFont(family: string, size: number, weight: number, style: string): Font {
const key = `${family}_${size}_${weight}_${style}`;
if (!this.fonts.has(key)) {
console.log(`Creating new font: ${key}`);
this.fonts.set(key, new Font(family, size, weight, style));
}
return this.fonts.get(key)!;
}
getFontCount(): number {
return this.fonts.size;
}
}
// Usage
const fontFactory = new FontFactory();
const text = "Hello World";
// Avant Flyweight : chaque caractère a sa propre police (12 objets font identiques)
for (const ch of text) {
const font = new Font("Arial", 12, 400, "normal");
new Character(ch, font);
}
// Avec Flyweight : police partagée (1 seul objet font)
const sharedFont = fontFactory.getFont("Arial", 12, 400, "normal");
for (const ch of text) {
new Character(ch, sharedFont);
}
console.log(`Font instances created: ${fontFactory.getFontCount()}`);
Partie 4 : Comparaison
| Critère | Composite | Facade | Flyweight |
|---|---|---|---|
| Objectif | Traiter arbres uniformément | Simplifier l'API | Optimiser la mémoire |
| Structure | Arbre | Interface unifiée | Pool d'objets partagés |
| Relation | Partie-tout | Client-sous-système | Intrinsèque-extrinsèque |
| Usage | UI, fichiers, menus | Bibliothèques, APIs | Polices, textures, données |