Dùng Decorator: bọc object gốc trong một object cùng interface, thêm hành vi trước/sau rồi ủy quyền phần việc chính cho object bên trong.
interface PriceApi { get(sku: string): Promise<number> }
class CachedPriceApi implements PriceApi {
constructor(private inner: PriceApi, private cache: Cache) {}
async get(sku: string) {
const hit = await this.cache.get(sku)
if (hit !== null) return hit
const value = await this.inner.get(sku)
await this.cache.set(sku, value, 60)
return value
}
}
const api = new TimedPriceApi(new CachedPriceApi(new HttpPriceApi(), cache), metrics)Mỗi mối quan tâm nằm một lớp riêng, ghép được theo thứ tự tùy ý và class gốc không đổi một dòng nào.
Khác kế thừa: kế thừa cố định tổ hợp lúc biên dịch, và mỗi tổ hợp cache/retry/metrics là một subclass mới. Decorator ghép lúc chạy, số tổ hợp không làm nổ số class.
Lưu ý thực tế:
- Thứ tự bọc quyết định ngữ nghĩa: retry ngoài cache thì retry cả lần đọc cache; cache ngoài retry thì kết quả lỗi không bị cache.
- Chồng nhiều lớp làm stack trace khó đọc — giữ số decorator ở mức tối thiểu và đặt tên class rõ ràng.
- Trong JS còn cách nhẹ hơn là hàm bậc cao (withRetry(fn)) khi interface chỉ có một method.