1. Tổng quan Structural Patterns
Nhóm này tập trung vào cách các class và object được tổ chức thành cấu trúc lớn hơn. Mỗi pattern giải một vấn đề tổ chức cụ thể: hợp nhất API không tương thích (Adapter), tách abstraction khỏi implementation (Bridge), xử lý cây (Composite), thêm chức năng động (Decorator), v.v.
| Pattern | Một câu |
|---|---|
| Adapter | Chuyển interface không tương thích → tương thích |
| Bridge | Tách abstraction (cái gì) khỏi implementation (cách) |
| Composite | Cây object — leaf và composite cùng interface |
| Decorator | Thêm trách nhiệm động bằng cách bao bọc |
| Facade | Một interface đơn giản che hệ con phức tạp |
| Flyweight | Chia sẻ object để tiết kiệm memory |
| Proxy | Object đại diện kiểm soát truy cập đến object thật |
2. Adapter
Adapter
Chuyển interface của một class thành interface khác mà client mong đợi. Cho phép class hoạt động cùng nhau mà không sửa code.
2.1. Use case kinh điển
App của bạn dùng PaymentProcessor nội bộ. Sếp bảo tích hợp Stripe — nhưng Stripe SDK có signature khác:
// Interface app dùng
interface PaymentProcessor {
pay(amount: number, currency: string): Promise<{ id: string }>;
}
// Stripe SDK (third-party)
class StripeAPI {
async createCharge(opts: { amountCents: number; currency: string; source: string }) {
return { charge_id: 'ch_' + Date.now() };
}
}
// Adapter
class StripeAdapter implements PaymentProcessor {
constructor(private stripe: StripeAPI, private source: string) {}
async pay(amount: number, currency: string) {
const result = await this.stripe.createCharge({
amountCents: Math.round(amount * 100),
currency: currency.toLowerCase(),
source: this.source,
});
return { id: result.charge_id };
}
}
// App dùng adapter, không biết về Stripe
const processor: PaymentProcessor = new StripeAdapter(new StripeAPI(), 'tok_xxx');
await processor.pay(100, 'USD');
2.2. Class adapter vs Object adapter
- Class adapter — kế thừa từ adaptee + implement target interface. Cần multiple inheritance.
- Object adapter — chứa adaptee như field (composition). Linh hoạt hơn — TypeScript chỉ làm được cách này.
2.3. Khi nào dùng
- Tích hợp third-party API có signature khác.
- Migrate dần từ legacy class sang interface mới.
- Test: tạo Adapter cho mock implementation.
3. Bridge
Bridge
Tách abstraction khỏi implementation để cả hai có thể vary độc lập.
3.1. Vấn đề: class explosion
App vẽ shape (Circle, Square, Triangle) trên nhiều platform render (Canvas, SVG, WebGL). Cách "naive":
3.2. Bridge giải quyết
// Implementation hierarchy (Renderer)
interface Renderer {
drawCircle(x: number, y: number, r: number): void;
drawRect(x: number, y: number, w: number, h: number): void;
}
class CanvasRenderer implements Renderer {
drawCircle(x: number, y: number, r: number) { console.log(`Canvas: circle (${x},${y}) r=${r}`); }
drawRect(x: number, y: number, w: number, h: number) { console.log(`Canvas: rect`); }
}
class SvgRenderer implements Renderer {
drawCircle(x: number, y: number, r: number) { console.log(`SVG: <circle/>`); }
drawRect(x: number, y: number, w: number, h: number) { console.log(`SVG: <rect/>`); }
}
// Abstraction hierarchy (Shape)
abstract class Shape {
constructor(protected renderer: Renderer) {}
abstract draw(): void;
}
class Circle extends Shape {
constructor(renderer: Renderer, private x: number, private y: number, private r: number) {
super(renderer);
}
draw() { this.renderer.drawCircle(this.x, this.y, this.r); }
}
class Square extends Shape {
constructor(renderer: Renderer, private x: number, private y: number, private size: number) {
super(renderer);
}
draw() { this.renderer.drawRect(this.x, this.y, this.size, this.size); }
}
// Sử dụng:
const canvas = new CanvasRenderer();
const svg = new SvgRenderer();
new Circle(canvas, 10, 10, 5).draw();
new Circle(svg, 10, 10, 5).draw(); // cùng circle, khác renderer
3 shape + 3 renderer = 6 class (thay vì 9). Thêm Pentagon → 1 class. Thêm WebGL renderer → 1 class.
3.3. Bridge vs Adapter
Adapter: fix sau — class đã tồn tại không tương thích, ta wrap. Bridge: thiết kế trước — biết hệ sẽ có 2 chiều biến đổi, tách từ đầu.
4. Composite
Composite
Compose object thành cấu trúc cây để biểu diễn hierarchy part-whole. Client xử lý leaf và composite một cách đồng nhất.
4.1. Implementation
interface FileSystemNode {
name: string;
size(): number;
print(indent?: string): void;
}
class File implements FileSystemNode {
constructor(public name: string, private bytes: number) {}
size() { return this.bytes; }
print(indent = '') { console.log(`${indent}📄 ${this.name} (${this.bytes}B)`); }
}
class Folder implements FileSystemNode {
private children: FileSystemNode[] = [];
constructor(public name: string) {}
add(node: FileSystemNode): this {
this.children.push(node);
return this;
}
size(): number {
return this.children.reduce((sum, c) => sum + c.size(), 0);
}
print(indent = '') {
console.log(`${indent}📁 ${this.name}/ (${this.size()}B total)`);
this.children.forEach(c => c.print(indent + ' '));
}
}
// Sử dụng:
const root = new Folder('project')
.add(new File('package.json', 500))
.add(new Folder('src')
.add(new File('index.ts', 1500))
.add(new File('utils.ts', 800))
);
root.print();
console.log('Total size:', root.size());
4.2. Use cases thực tế
- File system (như trên).
- UI tree:
ComponentvớiContainerchứaComponentcon. - HTML/XML DOM.
- Tổ chức công ty (employee, team, department).
- Math expression: số (leaf) và biểu thức (composite).
- Menu hierarchy.
4.3. Variant: Composite với uniform vs safe interface
- Uniform: cả leaf và composite có cùng API (kể cả
add,remove) — gọifile.add(...)không lỗi compile, nhưng có thể no-op hoặc throw. - Safe: chỉ composite có
add/remove— type-safe nhưng client phải check type.
5. Decorator
Decorator
Thêm trách nhiệm cho object động bằng cách bao bọc trong wrapper. Linh hoạt hơn nhiều inheritance.
5.1. Vấn đề: combinatorial inheritance
class Coffee { cost() { return 5; } }
class CoffeeWithMilk extends Coffee { cost() { return super.cost() + 1; } }
class CoffeeWithSugar extends Coffee { cost() { return super.cost() + 0.5; } }
class CoffeeWithMilkAndSugar extends CoffeeWithMilk { /* + 0.5 */ }
class CoffeeWithMilkAndSugarAndCaramel extends ... ;
// Combinatorial explosion! 3 topping = 8 class
5.2. Decorator giải
interface Beverage {
cost(): number;
description(): string;
}
class Coffee implements Beverage {
cost() { return 5; }
description() { return 'Coffee'; }
}
class Tea implements Beverage {
cost() { return 3; }
description() { return 'Tea'; }
}
// Decorator base
abstract class BeverageDecorator implements Beverage {
constructor(protected wrapped: Beverage) {}
abstract cost(): number;
abstract description(): string;
}
class WithMilk extends BeverageDecorator {
cost() { return this.wrapped.cost() + 1; }
description() { return this.wrapped.description() + ' + milk'; }
}
class WithSugar extends BeverageDecorator {
cost() { return this.wrapped.cost() + 0.5; }
description() { return this.wrapped.description() + ' + sugar'; }
}
class WithCaramel extends BeverageDecorator {
cost() { return this.wrapped.cost() + 2; }
description() { return this.wrapped.description() + ' + caramel'; }
}
// Tổ hợp tự do:
let drink: Beverage = new Coffee();
drink = new WithMilk(drink);
drink = new WithSugar(drink);
drink = new WithCaramel(drink);
console.log(drink.description()); // Coffee + milk + sugar + caramel
console.log(drink.cost()); // 8.5
5.3. Use cases mạnh
- Java I/O:
BufferedInputStream(GzipInputStream(FileInputStream(file)))— combo decorator. - Express middleware:
app.use(logger).use(auth).use(cors)— chuỗi handler. - HOC trong React:
withAuth(withLogging(MyComponent)). - Logging/caching/retry quanh function call.
5.4. Decorator vs Inheritance
Inheritance "đúng" với is-a chuyên biệt; Decorator "đúng" khi muốn mix-and-match feature. Decorator có thể thêm/bỏ runtime, inheritance không.
6. Facade
Facade
Cung cấp interface đơn giản và thống nhất cho một subsystem phức tạp.
6.1. Ví dụ
// Subsystem phức tạp với nhiều class
class VideoFile { constructor(public path: string) {} }
class CodecFactory { static extract(file: VideoFile) { return new MPEG4Codec(); } }
class MPEG4Codec {}
class OggCodec {}
class BitrateReader { static read(file: VideoFile, codec: any) { return Buffer.alloc(1024); } }
class AudioMixer { fix(buffer: Buffer) { return buffer; } }
// Facade
class VideoConverter {
convert(filename: string, format: string): Buffer {
const file = new VideoFile(filename);
const sourceCodec = CodecFactory.extract(file);
const destCodec = format === 'mp4' ? new MPEG4Codec() : new OggCodec();
const buffer = BitrateReader.read(file, sourceCodec);
const mixer = new AudioMixer();
return mixer.fix(buffer);
}
}
// Client chỉ cần:
const converter = new VideoConverter();
const result = converter.convert('video.avi', 'mp4');
6.2. Use cases hiện đại
- SDK: AWS SDK
s3.upload()ẩn nhiều bước (multipart, retry, signing). - Service layer trong app:
OrderService.checkout()orchestrate Repository, Mailer, Inventory, Payment. - API gateway: 1 endpoint gọi nhiều microservice phía sau.
Facade là pattern tự nhiên — bạn đã viết Facade rất nhiều lần mà không gọi tên.
7. Flyweight
Flyweight
Chia sẻ object để hỗ trợ số lượng cực lớn object nhỏ một cách hiệu quả về memory.
7.1. Khái niệm intrinsic vs extrinsic
- Intrinsic state — không đổi, có thể chia sẻ (vd: sprite của particle, mesh của tree).
- Extrinsic state — riêng từng instance (vd: vị trí, rotation của particle).
7.2. Implementation
// Heavy data — share được
class TreeType {
constructor(
public readonly name: string,
public readonly color: string,
public readonly texture: ArrayBuffer, // 1 MB texture
) {}
draw(x: number, y: number) {
// Render với x, y (extrinsic) + texture (intrinsic)
}
}
// Flyweight factory đảm bảo TreeType chia sẻ
class TreeTypeFactory {
private static cache = new Map<string, TreeType>();
static get(name: string, color: string, texture: ArrayBuffer): TreeType {
const key = `${name}|${color}`;
if (!this.cache.has(key)) {
this.cache.set(key, new TreeType(name, color, texture));
}
return this.cache.get(key)!;
}
}
// Tree thật — chỉ có position
class Tree {
constructor(
public x: number,
public y: number,
public type: TreeType, // shared!
) {}
draw() { this.type.draw(this.x, this.y); }
}
// Forest 1 triệu cây nhưng chỉ 5 loại
const forest: Tree[] = [];
for (let i = 0; i < 1_000_000; i++) {
const type = TreeTypeFactory.get('Oak', 'green', oakTexture);
forest.push(new Tree(Math.random() * 1000, Math.random() * 1000, type));
}
// 1M tree × 16 byte (2 number + ref) = 16 MB
// Nếu mỗi tree giữ texture 1MB → 1 TB!
7.3. Use cases thực
- Game: particle system, terrain, characters.
- Text editor: glyph cache (1 char "A" có cùng glyph data, khác chỉ position).
- Web browser: tab cache shared state.
- String interning trong Java/.NET — string literal giống nhau dùng chung 1 instance.
Trong app web thông thường ít gặp. Khi gặp → giảm RAM 100×.
8. Proxy
Proxy
Cung cấp object đại diện kiểm soát truy cập đến object khác. Cùng interface với object thật.
8.1. Các loại Proxy
- Virtual Proxy — lazy initialization (chỉ khởi tạo khi cần).
- Protection Proxy — kiểm soát quyền truy cập.
- Caching Proxy — lưu kết quả gọi.
- Logging Proxy — ghi log mọi call.
- Remote Proxy — đại diện object trên server khác (RPC).
- Smart Reference — count reference, cleanup tự động.
8.2. Virtual Proxy — lazy loading
interface Image {
display(): void;
}
class HighResImage implements Image {
constructor(private filename: string) {
console.log(`Loading ${filename} from disk...`); // chậm
}
display() { console.log(`Displaying ${this.filename}`); }
}
class ImageProxy implements Image {
private real: HighResImage | null = null;
constructor(private filename: string) {}
display() {
if (!this.real) {
this.real = new HighResImage(this.filename); // lazy load
}
this.real.display();
}
}
// Tạo 100 ảnh — không load disk
const images = Array.from({ length: 100 }, (_, i) => new ImageProxy(`img${i}.jpg`));
// Khi user scroll đến ảnh 5 → load
images[5].display();
8.3. Caching Proxy
interface UserApi {
getUser(id: string): Promise<User>;
}
class HttpUserApi implements UserApi {
async getUser(id: string) {
return fetch(`/api/users/${id}`).then(r => r.json());
}
}
class CachedUserApi implements UserApi {
private cache = new Map<string, User>();
constructor(private real: UserApi) {}
async getUser(id: string) {
if (this.cache.has(id)) return this.cache.get(id)!;
const user = await this.real.getUser(id);
this.cache.set(id, user);
return user;
}
}
// Client không biết có cache:
const api: UserApi = new CachedUserApi(new HttpUserApi());
8.4. JavaScript Proxy native
JS có Proxy built-in cho meta-programming:
const target = { name: 'Alice', age: 30 };
const proxy = new Proxy(target, {
get(obj, prop) {
console.log(`Reading ${String(prop)}`);
return obj[prop];
},
set(obj, prop, value) {
if (prop === 'age' && value < 0) throw new Error('age >= 0');
obj[prop] = value;
return true;
},
});
proxy.name; // logs "Reading name"
proxy.age = -5; // throws
Vue 3 reactivity, Mobx, Immer dùng Proxy native cho dirty tracking.
8.5. Proxy vs Decorator
Cấu trúc giống — đều wrap object cùng interface. Khác ở ý đồ:
- Decorator: thêm chức năng (cộng dồn).
- Proxy: kiểm soát truy cập (gating).
Trong thực tế ranh giới mờ — chọn tên theo intent.
9. So sánh nhanh 7 Structural Patterns
| Pattern | Cấu trúc | Intent |
|---|---|---|
| Adapter | Wrap với interface khác | Tương thích interface |
| Bridge | 2 hierarchy song song nối qua composition | Tách abstraction khỏi implementation |
| Composite | Cây với leaf và composite cùng interface | Xử lý đồng nhất phần và toàn thể |
| Decorator | Wrap cùng interface, cộng dồn | Thêm chức năng động |
| Facade | 1 class che subsystem | Đơn giản hóa giao diện |
| Flyweight | Chia sẻ object intrinsic | Tiết kiệm memory |
| Proxy | Wrap cùng interface, gating | Kiểm soát truy cập |
10. Bài tập
- Adapter: bạn có legacy class
OldLogger.write(level, msg, time). App mới mong đợiLogger.log(msg). Viết adapter. - Bridge: app vẽ Shape (Circle, Square) trên Canvas/SVG. Implement Bridge với 2 hierarchy. Thêm Triangle và WebGL renderer — đếm số class mới.
- Composite: file system với File và Folder. Method
find(name)trả về node có tên đó (recursive).print()với indentation. - Decorator: Coffee với 3 topping (milk, sugar, caramel). Implement Decorator. Tạo "Latte triple sugar" và in ra cost + description.
- Facade: bạn có 4 class
OrderRepository,InventoryService,PaymentGateway,EmailService. TạoCheckoutFacadevới 1 methodcheckout(order)orchestrate. - Flyweight: app render 100k dot trong canvas, mỗi dot có (x, y, color). Có 5 màu khác nhau. Áp dụng Flyweight để chia sẻ Color object.
- Proxy: tạo CachedHttpProxy wrap fetch API. Cache TTL 60 giây. Demo benefit.
- Phân biệt với ví dụ: Adapter vs Decorator vs Proxy.
11. Quiz
Quiz cuối Chương 5
Adapter pattern phù hợp khi:
Bridge pattern khác Adapter ở:
Composite pattern hữu ích khi:
Decorator vs Inheritance:
Facade pattern:
Flyweight tách "intrinsic" và "extrinsic":
Proxy KHÔNG phù hợp dùng cho:
JavaScript Proxy (native):
new Proxy(target, handler) với handler có get/set/apply trap. Vue 3 reactivity, MobX state, Immer immutable updates đều xây trên Proxy. Native JS, không phải pattern Proxy GoF nhưng cùng tên.Hoàn thành Chương 5 (12/23 patterns). Tiếp theo: Chương 6 — Behavioral Part 1 (6 mẫu) →