PHpullh

TYPESCRIPT · 심층 가이드

TypeScript 패턴 완전 정리

GoF 패턴을 TypeScript로 옮기면 상당수가 클래스 계층 대신 함수 타입과 판별 유니온으로 줄어들어, 원래 의도만 남고 골격은 훨씬 가벼워집니다.

주제 20개 · 예제 코드 포함 · 최종 수정 2026-08-30 · 작성 pullh 편집팀

구조적 타이핑과 일급 함수가 있는 언어에서는 패턴의 무게중심이 달라집니다. 전략을 담기 위해 인터페이스와 구현 클래스를 세 개 만들 필요 없이 함수 시그니처 하나면 충분한 경우가 많고, 상태 전이는 클래스 대신 태그 필드를 가진 유니온으로 표현하면 컴파일러가 누락된 분기를 대신 찾아 줍니다. 자바 계열 교재를 그대로 옮겨 적으면 실제로는 아무것도 얻지 못한 껍데기만 남는 이유가 여기 있습니다.

설정 객체로 옵션을 하나의 타입으로 묶는 감각을 먼저 잡고, 동작 자체를 인자로 넘기는 전략 패턴으로 넘어가면 뒤따르는 패턴 대부분이 그 변형으로 읽힙니다. 생성 책임만 떼어 내는 팩터리 메서드, 같은 형태의 함수를 겹겹이 쌓는 미들웨어 체인, 그리고 그 조립 과정을 한곳에 모으는 의존성 주입 컨테이너가 차례로 이어집니다. 저장소 패턴은 여기에 데이터 접근 경계를 더해, 앞의 세 조각이 실제 애플리케이션에서 어떻게 맞물리는지를 보여 줍니다.

직접 만든 DI 컨테이너에서 흔히 놓치는 부분은 등록소가 Map<string, unknown>이 되는 순간 타입 안전성이 사라진다는 점입니다. 꺼낼 때마다 단언을 붙이게 되고, 잘못 등록한 의존성은 실행 중에야 드러납니다. 키를 문자열 대신 타입을 실어 나르는 토큰으로 두면 조회 시점에 타입이 따라옵니다. 빌더에서 this를 반환하는 체이닝도 상속과 만나면 부모 타입으로 좁혀지니 주의가 필요합니다.

01설정 객체

TypeScript에서 설정 객체 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 설정 객체
const strategies = { percent: (value: number) => value * 0.9, flat: (value: number) => value - 5 };
console.log(strategies.percent(100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

02전략 패턴

TypeScript에서 전략 패턴 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 전략 패턴
const listeners = [];
const subscribe = (fn) => listeners.push(fn);
const publish = (value: string) => listeners.forEach(fn => fn(value));
subscribe(console.log);
publish("update-2");
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

03옵저버

TypeScript에서 옵저버 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 옵저버
const cache = new Map();
const getUser = (id: number) => { if (!cache.has(id)) cache.set(id, { id, name: "user" }); return cache.get(id); };
console.log(getUser(3));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

04데코레이터

TypeScript에서 데코레이터 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 데코레이터
const middlewares = [(ctx, next) => { ctx.steps.push("a"); return next(); }, (ctx, next) => { ctx.steps.push("b"); return next(); }];
const ctx = { steps: [] };
let index = -1;
const run = (i) => { if (i <= index) throw new Error("twice"); index = i; const mw = middlewares[i]; if (mw) return mw(ctx, () => run(i + 1)); };
run(0);
console.log(ctx.steps);
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

05어댑터

TypeScript에서 어댑터 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 어댑터
const strategies = { percent: (value: number) => value * 0.9, flat: (value: number) => value - 5 };
console.log(strategies.percent(100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

06커맨드

TypeScript에서 커맨드 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 커맨드
const listeners = [];
const subscribe = (fn) => listeners.push(fn);
const publish = (value: string) => listeners.forEach(fn => fn(value));
subscribe(console.log);
publish("update-6");
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

07상태 패턴

TypeScript에서 상태 패턴 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 상태 패턴
const cache = new Map();
const getUser = (id: number) => { if (!cache.has(id)) cache.set(id, { id, name: "user" }); return cache.get(id); };
console.log(getUser(7));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

08팩터리 메서드

TypeScript에서 팩터리 메서드 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 팩터리 메서드
const middlewares = [(ctx, next) => { ctx.steps.push("a"); return next(); }, (ctx, next) => { ctx.steps.push("b"); return next(); }];
const ctx = { steps: [] };
let index = -1;
const run = (i) => { if (i <= index) throw new Error("twice"); index = i; const mw = middlewares[i]; if (mw) return mw(ctx, () => run(i + 1)); };
run(0);
console.log(ctx.steps);
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

09빌더 패턴

TypeScript에서 빌더 패턴 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 빌더 패턴
const strategies = { percent: (value: number) => value * 0.9, flat: (value: number) => value - 5 };
console.log(strategies.percent(100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

10캐시 래퍼

TypeScript에서 캐시 래퍼 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 캐시 래퍼
const listeners = [];
const subscribe = (fn) => listeners.push(fn);
const publish = (value: string) => listeners.forEach(fn => fn(value));
subscribe(console.log);
publish("update-10");
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

11retry 유틸

TypeScript에서 retry 유틸 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// retry 유틸
const cache = new Map();
const getUser = (id: number) => { if (!cache.has(id)) cache.set(id, { id, name: "user" }); return cache.get(id); };
console.log(getUser(11));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

12미들웨어 체인

TypeScript에서 미들웨어 체인 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 미들웨어 체인
const middlewares = [(ctx, next) => { ctx.steps.push("a"); return next(); }, (ctx, next) => { ctx.steps.push("b"); return next(); }];
const ctx = { steps: [] };
let index = -1;
const run = (i) => { if (i <= index) throw new Error("twice"); index = i; const mw = middlewares[i]; if (mw) return mw(ctx, () => run(i + 1)); };
run(0);
console.log(ctx.steps);
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

13검증기 조합

TypeScript에서 검증기 조합 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 검증기 조합
const strategies = { percent: (value: number) => value * 0.9, flat: (value: number) => value - 5 };
console.log(strategies.percent(100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

14포매터 파이프

TypeScript에서 포매터 파이프 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 포매터 파이프
const listeners = [];
const subscribe = (fn) => listeners.push(fn);
const publish = (value: string) => listeners.forEach(fn => fn(value));
subscribe(console.log);
publish("update-14");
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

15페이지네이션

TypeScript에서 페이지네이션 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 페이지네이션
const cache = new Map();
const getUser = (id: number) => { if (!cache.has(id)) cache.set(id, { id, name: "user" }); return cache.get(id); };
console.log(getUser(15));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

16데이터 매퍼

TypeScript에서 데이터 매퍼 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 데이터 매퍼
const middlewares = [(ctx, next) => { ctx.steps.push("a"); return next(); }, (ctx, next) => { ctx.steps.push("b"); return next(); }];
const ctx = { steps: [] };
let index = -1;
const run = (i) => { if (i <= index) throw new Error("twice"); index = i; const mw = middlewares[i]; if (mw) return mw(ctx, () => run(i + 1)); };
run(0);
console.log(ctx.steps);
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

17저장소 패턴

TypeScript에서 저장소 패턴 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 저장소 패턴
const strategies = { percent: (value: number) => value * 0.9, flat: (value: number) => value - 5 };
console.log(strategies.percent(100));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

18의존성 주입 컨테이너

TypeScript에서 의존성 주입 컨테이너 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 의존성 주입 컨테이너
const listeners = [];
const subscribe = (fn) => listeners.push(fn);
const publish = (value: string) => listeners.forEach(fn => fn(value));
subscribe(console.log);
publish("update-18");
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

19템플릿 메서드

TypeScript에서 템플릿 메서드 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 템플릿 메서드
const cache = new Map();
const getUser = (id: number) => { if (!cache.has(id)) cache.set(id, { id, name: "user" }); return cache.get(id); };
console.log(getUser(19));
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

20파사드

TypeScript에서 파사드 개념을 코드로 빠르게 확인하는 패턴 예제입니다.

TypeScript code

// 파사드
const middlewares = [(ctx, next) => { ctx.steps.push("a"); return next(); }, (ctx, next) => { ctx.steps.push("b"); return next(); }];
const ctx = { steps: [] };
let index = -1;
const run = (i) => { if (i <= index) throw new Error("twice"); index = i; const mw = middlewares[i]; if (mw) return mw(ctx, () => run(i + 1)); };
run(0);
console.log(ctx.steps);
알아두면 좋은 점

패턴은 이름보다 책임 분리와 교체 가능성에 주목하면 더 실용적으로 이해됩니다.

자주 하는 실수

패턴 이름만 따라 하고 결합도를 줄이지 못하면 구조가 오히려 더 복잡해집니다.

정리하며

  • 전략이 함수 하나로 표현된다면 인터페이스와 구현 클래스를 따로 만들지 않습니다.
  • 상태 전이는 판별 유니온으로 두어 분기 누락을 컴파일러가 잡게 합니다.
  • DI 컨테이너의 키는 문자열 대신 타입을 실은 토큰으로 두어 단언을 없앱니다.
  • 미들웨어는 입력과 출력 형태를 통일해야 순서를 바꿔도 조합이 유지됩니다.

더 깊이 들어가고 싶다면 TypeScript 학습 라이브러리에서 다른 주제 가이드를 이어서 보거나, 언어 비교에서 같은 개념이 다른 언어에서 어떻게 표현되는지 확인해 보세요.