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 giao 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 concern nằm ở một lớp riêng, ghép được theo thứ tự tuỳ ý 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 số class tăng vọt.
Lưu ý:
- 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.