같은 인터페이스를 구현한 래퍼로 객체를 감싸, 원래 객체를 고치지 않고 앞뒤에 기능을 덧붙이는 패턴. 래퍼를 여러 겹 쌓아 기능을 조합할 수 있다.
interface Fetcher { get(url: string): Promise<Response> }
const withLogging = (inner: Fetcher): Fetcher => ({
async get(url) {
console.time(url)
try { return await inner.get(url) } finally { console.timeEnd(url) }
},
})
const withRetry = (inner: Fetcher, times = 3): Fetcher => ({
async get(url) {
for (let i = 1; ; i++) {
try { return await inner.get(url) } catch (e) { if (i >= times) throw e }
}
},
})
const fetcher = withLogging(withRetry(baseFetcher))- 상속으로 "로깅되는 재시도 fetcher" 같은 조합을 만들면 조합 수만큼 클래스가 폭발한다. 데코레이터는 조합을 실행 시점에 고른다(상속보다 위임)
- 감싸는 순서가 결과에 영향을 준다. 위 예에서 순서를 바꾸면 재시도마다 로그가 찍힌다
- 여러 겹이면 디버깅할 때 호출 흐름을 따라가기 어렵다
- 입출력 스트림에 버퍼링·압축·암호화를 겹치는 것이 고전적인 예이고, React의 고차 컴포넌트(Higher-Order Component, HOC)도 같은 모양이다
언어의 데코레이터 문법과 구분
파이썬의 @decorator(파이썬 데코레이터)나 TypeScript 데코레이터(TypeScript 데코레이터)는 함수·클래스를 감싸는 문법이다. 이 패턴을 구현하는 데 쓸 수 있지만 같은 것은 아니다. 패턴은 객체를 실행 중에 감싸는 설계이고, 문법은 정의 시점에 변환을 적용하는 도구다.