From f673fccfd621d69b17e144b10751e71f78417133 Mon Sep 17 00:00:00 2001 From: jihyeon Date: Tue, 26 May 2026 20:45:18 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20=EB=A7=88=EC=A7=80=EB=A7=89=20=EC=A3=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...5\355\225\251_\355\214\250\355\204\264.md" | 250 ++++++++++ ...0\354\235\270_\355\214\250\355\204\264.md" | 333 +++++++++++++ ...0\355\203\200_\355\214\250\355\204\264.md" | 457 ++++++++++++++++++ 3 files changed, 1040 insertions(+) create mode 100644 "jihyeon/12.\353\263\265\355\225\251_\355\214\250\355\204\264.md" create mode 100644 "jihyeon/13.\354\213\244\354\240\204_\353\224\224\354\236\220\354\235\270_\355\214\250\355\204\264.md" create mode 100644 "jihyeon/14.\352\270\260\355\203\200_\355\214\250\355\204\264.md" diff --git "a/jihyeon/12.\353\263\265\355\225\251_\355\214\250\355\204\264.md" "b/jihyeon/12.\353\263\265\355\225\251_\355\214\250\355\204\264.md" new file mode 100644 index 0000000..f9a3758 --- /dev/null +++ "b/jihyeon/12.\353\263\265\355\225\251_\355\214\250\355\204\264.md" @@ -0,0 +1,250 @@ +# 복합 패턴 + +**복합 패턴(Compound Pattern)**은 여러 디자인 패턴을 함께 사용해 하나의 복잡한 설계를 구성하는 방식이다. + +Head First Design Patterns 12장은 오리 시뮬레이터 예제를 통해 어댑터, 데코레이터, 팩토리, 컴포지트, 옵저버가 한 구조 안에서 협력하는 과정을 보여준다. + +프론트엔드에서는 하나의 화면이나 기능 안에 API 연동, 외부 SDK, 상태 관리, 이벤트 처리, 로깅, UI 조합이 함께 등장할 때 같은 관점이 적용된다. + +## 책의 내용 + +12장의 오리 시뮬레이터는 다음 요구사항을 단계적으로 해결한다. + +| 요구사항 | 사용 패턴 | 역할 | +| --------------------------- | ---------- | ---------------------------------------- | +| 거위를 오리처럼 다루기 | 어댑터 | 다른 인터페이스를 기존 인터페이스에 맞춤 | +| 꽥꽥 횟수 세기 | 데코레이터 | 기존 객체에 부가 기능 추가 | +| 카운터 적용 누락 방지 | 팩토리 | 생성과 조립 규칙을 한 곳에서 관리 | +| 오리 무리를 하나처럼 다루기 | 컴포지트 | 개별 객체와 그룹을 동일하게 처리 | +| 꽥꽥 이벤트 알림 | 옵저버 | 상태 변화와 반응을 분리 | + +단일 패턴 하나로 모든 요구사항을 해결하지 않는다. 각 문제에 맞는 패턴을 사용하고, 최종적으로 하나의 동작 흐름으로 조합한다. + +--- + +## 프론트엔드 대응 + +프론트엔드 기능도 여러 책임이 섞이면 복합 패턴 구조가 필요해진다. + +| 책의 예제 | 프론트엔드 대응 | 예시 | +| --------------------- | -------------------------- | -------------------------------------------------------- | +| `Quackable` | 공통 인터페이스 | 공통 Props, 공통 API 타입, 공통 훅 반환 타입 | +| `GooseAdapter` | 외부 형식 변환 | SDK 어댑터, API 응답 변환, 레거시 컴포넌트 래핑 | +| `QuackCounter` | 부가 책임 추가 | analytics, logging, permission check, retry wrapper | +| `CountingDuckFactory` | 생성 규칙 고정 | API client 생성기, provider 조립 함수, feature flag 분기 | +| `Flock` | 그룹과 개별 요소 동일 처리 | 메뉴 트리, 라우트 트리, 폼 필드 그룹 | +| `Quackologist` | 상태 변화 관찰 | store 구독, query invalidation, WebSocket listener | + +핵심은 화면 컴포넌트가 모든 구현 세부사항을 직접 알지 않게 만드는 것이다. + +```text +컴포넌트: 렌더링과 사용자 입력 +어댑터: 외부 API, SDK, 레거시 형식 변환 +데코레이터: 로깅, 트래킹, 권한 검사 같은 부가 책임 +팩토리: 생성과 조립 규칙 +컴포지트: 개별 요소와 그룹의 동일 처리 +옵저버: 상태 변화와 반응 분리 +``` + +--- + +## 패턴별 역할 + +### 1. 어댑터 + +외부 인터페이스를 내부에서 사용하는 형태로 변환한다. + +주요 사용 지점: + +- REST API 응답을 화면 모델로 변환 +- GraphQL 응답을 도메인 객체로 변환 +- 외부 결제, 지도, 인증 SDK를 내부 인터페이스로 래핑 +- 레거시 컴포넌트를 디자인 시스템 컴포넌트처럼 사용 +- 브라우저 API를 테스트 가능한 함수로 감싸기 + +```typescript +function adaptUserResponse(response: UserApiResponse): UserProfile { + return { + id: response.user_id, + name: response.display_name, + avatarUrl: response.profile_image_url, + }; +} +``` + +어댑터의 목적은 외부 형식이 화면 코드 전체로 퍼지는 것을 막는 것이다. + +### 2. 데코레이터 + +기존 동작을 직접 수정하지 않고 바깥에서 부가 책임을 추가한다. + +주요 사용 지점: + +- analytics tracking +- logging +- permission check +- retry +- error boundary +- fetch wrapper +- React HOC + +```typescript +function withRetry( + fn: (...args: TArgs) => Promise, +) { + return async (...args: TArgs) => { + try { + return await fn(...args); + } catch { + return fn(...args); + } + }; +} +``` + +데코레이터의 목적은 원래 책임과 부가 책임을 분리하는 것이다. + +### 3. 팩토리 + +객체나 함수의 생성 규칙을 한 곳에서 관리한다. + +주요 사용 지점: + +- API client 생성 +- 테스트용 mock client 생성 +- 기능 플래그에 따른 구현체 선택 +- provider 조립 +- 컴포넌트 variant 생성 + +```typescript +function createApiClient(config: ApiConfig) { + return withAuth(withLogging(new HttpApiClient(config.baseUrl))); +} +``` + +팩토리의 목적은 조립 순서와 정책을 사용하는 쪽에 반복시키지 않는 것이다. + +### 4. 컴포지트 + +개별 요소와 그룹을 같은 방식으로 다룬다. + +주요 사용 지점: + +- 메뉴 트리 +- 라우트 트리 +- 폼 필드 그룹 +- 테이블 컬럼 그룹 +- 권한 트리 +- React children 기반 조합 + +```typescript +type MenuNode = MenuItem | MenuGroup; + +interface MenuItem { + type: "item"; + label: string; + href: string; +} + +interface MenuGroup { + type: "group"; + label: string; + children: MenuNode[]; +} +``` + +컴포지트의 목적은 중첩 구조를 일관된 방식으로 처리하는 것이다. + +### 5. 옵저버 + +상태를 변경하는 쪽과 상태 변화에 반응하는 쪽을 분리한다. + +주요 사용 지점: + +- React state update +- Zustand, Redux store 구독 +- TanStack Query cache subscription +- WebSocket event listener +- DOM event listener +- form watch +- analytics event pipeline + +```typescript +store.subscribe((state) => { + if (state.paymentStatus === "success") { + queryClient.invalidateQueries({ queryKey: ["order"] }); + } +}); +``` + +옵저버의 목적은 상태 변경 이후의 반응을 직접 결합하지 않는 것이다. + +--- + +## 적용 기준 + +복합 패턴은 다음 상황에서 유용하다. + +### 화면 컴포넌트가 많은 책임을 가질 때 + +API 호출, 응답 변환, 권한 검사, 트래킹, 상태 전파, UI 렌더링이 한 컴포넌트에 모이면 분리 대상이 된다. + +### 외부 API나 SDK가 여러 개 섞일 때 + +SDK마다 호출 방식과 응답 형식이 다르면 내부 공통 인터페이스를 두고 SDK별 차이는 어댑터에 둔다. + +### 동일한 부가 기능이 반복될 때 + +로깅, 인증 헤더, 에러 처리, 재시도 로직이 여러 곳에서 반복되면 wrapper나 데코레이터로 분리한다. + +### 생성 순서와 조립 규칙이 중요할 때 + +`base client -> auth wrapper -> retry wrapper -> logging wrapper`처럼 조립 순서가 정책이라면 팩토리로 고정한다. + +### 개별 요소와 그룹을 같은 방식으로 처리할 때 + +메뉴, 라우트, 폼, 권한 트리, 카테고리 트리처럼 중첩 구조가 있으면 컴포지트 관점이 적합하다. + +--- + +## 주의점 + +### 패턴 이름을 먼저 정하지 않기 + +중요한 기준은 패턴 이름이 아니라 책임의 위치다. + +```text +외부 형식이 화면에 퍼진다 -> 어댑터 +부가 기능이 반복된다 -> 데코레이터 +생성 규칙이 반복된다 -> 팩토리 +개별 요소와 그룹을 함께 다룬다 -> 컴포지트 +상태 변화와 반응이 결합된다 -> 옵저버 +``` + +### 커스텀 훅에 모든 책임을 넣지 않기 + +`useSomething()`은 조립 지점이 될 수 있지만 모든 책임의 최종 위치가 되면 구조가 다시 복잡해진다. + +### 요구사항이 단순할 때 과도하게 나누지 않기 + +결제 수단이 하나뿐인 화면에 `PaymentMethod`, `PaymentAdapter`, `PaymentFactory`, `PaymentGroup`을 모두 만들 필요는 없다. + +### 공통 인터페이스를 크게 만들지 않기 + +공통 인터페이스가 커지면 모든 구현체가 필요 없는 메서드까지 구현해야 한다. 역할별로 작게 유지하는 편이 안전하다. + +--- + +## 요약 + +복합 패턴은 여러 패턴을 나열하는 방식이 아니라, 여러 책임을 각자 맞는 위치에 배치하고 조합하는 설계 방식이다. + +프론트엔드에서는 화면이 커질수록 렌더링, 외부 연동, 상태 변화, 부가 기능, 생성 규칙이 한곳에 섞이기 쉽다. 복합 패턴 관점은 이 책임들을 분리하고, 화면 컴포넌트가 UI 흐름에 집중하도록 돕는다. + +핵심 정리: + +1. 어댑터는 외부 형식을 내부 형식으로 바꾼다. +2. 데코레이터는 기존 동작에 부가 책임을 추가한다. +3. 팩토리는 생성과 조립 규칙을 고정한다. +4. 컴포지트는 개별 요소와 그룹을 동일하게 처리한다. +5. 옵저버는 상태 변화와 반응을 분리한다. diff --git "a/jihyeon/13.\354\213\244\354\240\204_\353\224\224\354\236\220\354\235\270_\355\214\250\355\204\264.md" "b/jihyeon/13.\354\213\244\354\240\204_\353\224\224\354\236\220\354\235\270_\355\214\250\355\204\264.md" new file mode 100644 index 0000000..c0a49e3 --- /dev/null +++ "b/jihyeon/13.\354\213\244\354\240\204_\353\224\224\354\236\220\354\235\270_\355\214\250\355\204\264.md" @@ -0,0 +1,333 @@ +# 13장. 실전 디자인 패턴 + +| 책 내용 | 핵심 의미 | 프론트엔드 해석 | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------------------------ | +| 패턴은 공통 어휘다 | 문제, 해결책, 장단점을 이름으로 압축한다 | 코드 리뷰에서 설계 의도를 짧게 공유한다 | +| 패턴은 발견된 해결책이다 | 반복된 설계 문제에서 검증된 해결 방향이다 | 컴포넌트, 상태, API 흐름의 반복 변경 압력을 본다 | +| 패턴 카탈로그를 익힌다 | GoF 패턴을 생성, 구조, 행동 패턴으로 분류한다 | UI 조립, 상태/이벤트, API 연동, 변경 정책 기준으로 다시 읽는다 | +| 패턴은 만능 처방이 아니다 | 잘못 적용하면 구조가 복잡해진다 | 작은 화면에 과한 hook, provider, manager를 먼저 만들지 않는다 | +| 패턴 학습은 계속된다 | 책에 나온 패턴 외에도 변형이 많다 | React, Next.js, 상태관리, 디자인 시스템, API client 내부 패턴을 관찰한다 | + +> **패턴을 많이 쓰는 것보다 문제에 맞는 패턴을 필요한 만큼 쓰는 것** + +### 1. 패턴은 코드가 아니라 설계 언어다 + +전략 패턴을 배웠다고 해서 반드시 `Strategy` 클래스를 만들 필요는 없다. React에서는 함수 주입으로 전략 패턴의 의도를 표현할 수 있다. + +```typescript +type PriceFormatter = (price: number) => string; + +function ProductPrice({ + price, + formatPrice, +}: { + price: number; + formatPrice: PriceFormatter; +}) { + return {formatPrice(price)}; +} +``` + +핵심 질문은 다음과 같다. + +- 무엇이 자주 바뀌는가? +- 그 변화가 컴포넌트 내부에 섞여 있는가? +- 교체 가능한 정책으로 분리할 수 있는가? +- 분리 후 호출부가 단순해지는가? + +### 2. 패턴은 팀의 공통 어휘다 + +패턴 이름은 문제와 해결 방향을 함께 전달한다. + +- "이 분기 로직은 전략 패턴으로 분리할 수 있다." +- "서버 응답은 UI 모델로 바꾸는 어댑터가 필요하다." +- "상태 전이가 많으므로 reducer나 상태 패턴으로 명시할 수 있다." +- "디자인 시스템 컴포넌트는 컴포지트 구조로 조립 가능하게 설계할 수 있다." + +### 3. 패턴은 필요할 때만 쓴다 + +적용 순서는 다음과 같다. + +```text +변화 관찰 +→ 반복 변경 지점 확인 +→ 현재 구조로 감당 가능한지 확인 +→ 책임 분리 후보 선택 +→ 가장 작은 형태로 적용 +``` + +패턴 자체가 목적이 되면 작은 화면에도 과한 구조가 생길 수 있다. + +--- + +## 프론트엔드 적용 신호 + +| 신호 | 고려할 패턴 | +| ----------------------------------------- | ----------------------------------- | +| 같은 UI인데 정책만 다르다 | 전략, render props, props injection | +| 서버 응답과 UI 모델이 다르다 | 어댑터, mapper | +| 여러 컴포넌트가 같은 상태 변화를 구독한다 | 옵저버, external store, query cache | +| 복잡한 하위 시스템을 매번 직접 호출한다 | 퍼사드, custom hook | +| 버튼 클릭이 여러 실행 흐름으로 이어진다 | 커맨드, action object | +| 로딩/성공/실패/재시도 전이가 복잡하다 | 상태 패턴, reducer, state machine | +| UI 조합 규칙을 열어두고 싶다 | 컴포지트, compound component | + +### 조건문 증가 + +조건문이 계속 늘어나면 UI 정책이 컴포넌트에 과하게 결합된 신호다. + +```typescript +function SubmitButton({ user, plan, featureFlag }: Props) { + if (!user) return ; + if (!user.emailVerified) return ; + if (plan === "free" && !featureFlag.trial) return ; + if (plan === "pro") return ; + + return ; +} +``` + +이 경우 전략, 상태, 정책 객체, 설정 기반 렌더링으로 변화 지점을 분리할 수 있다. + +### API 응답 결합 + +서버 응답 형태가 화면에 직접 퍼지면 API 변경이 UI 변경으로 번진다. + +```typescript +return {response.user_profile.display_name}; +``` + +어댑터나 mapper로 UI 모델을 분리한다. + +```typescript +type UserViewModel = { + name: string; + avatarUrl: string; +}; + +function toUserViewModel(response: UserResponse): UserViewModel { + return { + name: response.user_profile.display_name, + avatarUrl: response.user_profile.avatar_url, + }; +} +``` + +--- + +## 프론트엔드 코드에서 보이는 패턴 + +### 전략 패턴 + +바뀌는 정책을 외부에서 주입한다. + +```typescript +const sortStrategies = { + latest: (items: Post[]) => + [...items].sort((a, b) => b.createdAt - a.createdAt), + popular: (items: Post[]) => [...items].sort((a, b) => b.likes - a.likes), +}; +``` + +정렬, 필터링, 가격 표시, 권한 정책, 폼 검증에 자주 쓰인다. + +### 옵저버 패턴 + +상태 변화가 여러 구독자에게 전파된다. + +```text +서버 데이터 변경 +→ 캐시 업데이트 +→ 구독 중인 컴포넌트 리렌더링 +``` + +DOM event listener, external store, React Query cache, WebSocket 구독에서 나타난다. + +### 데코레이터 패턴 + +기존 기능을 감싸 부가 기능을 추가한다. + +```typescript +async function fetchWithAuth(input: RequestInfo, init?: RequestInit) { + return fetch(input, { + ...init, + headers: { + ...init?.headers, + Authorization: `Bearer ${getAccessToken()}`, + }, + }); +} +``` + +HOC, wrapper component, middleware, fetch wrapper에서 나타난다. + +### 어댑터 패턴 + +외부 인터페이스를 앱 내부 모델에 맞춘다. + +```text +외부 SDK 응답 +→ adapter +→ 앱 내부 타입 +→ UI 컴포넌트 +``` + +API 응답, 브라우저 API, 외부 SDK, 디자인 시스템 경계에서 중요하다. + +### 퍼사드 패턴 + +복잡한 흐름을 단순한 입구로 감싼다. + +```typescript +function useCheckout() { + const cart = useCart(); + const payment = usePayment(); + const coupon = useCoupon(); + + return { + totalPrice: calculateTotal(cart.items, coupon.current), + submit: () => payment.pay(cart.items, coupon.current), + }; +} +``` + +화면 컴포넌트는 내부 모듈을 모두 알 필요 없이 `useCheckout()`만 사용한다. + +### 커맨드 패턴 + +사용자 요청을 실행 가능한 단위로 만든다. + +```typescript +type EditorCommand = { + execute(): void; + undo(): void; +}; +``` + +undo/redo, optimistic update, command palette, editor action에 적합하다. + +### 상태 패턴 + +상태별 데이터와 행동을 분리한다. + +```typescript +type AsyncState = + | { status: "idle" } + | { status: "loading" } + | { status: "success"; data: User } + | { status: "error"; message: string }; +``` + +로딩, 성공, 실패, 재시도 흐름이 복잡할 때 reducer나 state machine으로 명시한다. + +### 컴포지트 패턴 + +작은 UI 조각을 조립해 큰 UI를 만든다. + +```tsx + + + Profile + + ... + +``` + +compound component, slot 기반 UI, 디자인 시스템 컴포넌트에서 자주 나타난다. + +--- + +## 책에서 추가로 소개하는 패턴 + +| 패턴 | 책 내용 요약 | 프론트엔드 사용 사례 | +| ------------ | ---------------------------------------------------- | ---------------------------------------------------------- | +| 브리지 | 추상화와 구현을 분리해 독립적으로 변경 가능하게 한다 | headless UI와 styling layer 분리 | +| 빌더 | 복잡한 객체 생성 과정을 단계별로 분리한다 | query builder, form schema builder, test data builder | +| 책임 연쇄 | 요청을 여러 처리 단계에 순서대로 전달한다 | validation pipeline, request interceptor, middleware chain | +| 플라이웨이트 | 공통 상태를 공유해 메모리 사용을 줄인다 | virtualized list, canvas object, icon path, design token | +| 인터프리터 | 간단한 문법을 해석하는 객체 구조를 만든다 | route pattern, filter DSL, feature flag rule | +| 중재자 | 객체 간 상호작용을 한곳에서 조정한다 | dashboard coordinator, page-level controller hook | +| 메멘토 | 상태를 저장했다가 복원한다 | undo/redo, optimistic rollback, form draft restore | +| 프로토타입 | 기존 객체를 복제해 새 객체를 만든다 | config extends, theme variant, Storybook args | +| 방문자 | 데이터 구조를 순회하며 연산을 분리한다 | Babel visitor, ESLint rule visitor, AST transform | + +### 추가 패턴 한 줄 정리 + +- 브리지: 두 변화 축을 독립적으로 바꾸기 위해 분리한다. +- 빌더: 복잡한 생성 과정을 단계별 절차로 나눈다. +- 책임 연쇄: 하나의 요청을 여러 처리 단계에 통과시킨다. +- 플라이웨이트: 반복되는 공통 정보를 공유한다. +- 인터프리터: 문자열이나 규칙을 실행 가능한 의미로 바꾼다. +- 중재자: 여러 객체의 상호작용을 한곳에서 조정한다. +- 메멘토: 되돌리기 위해 현재 상태를 저장한다. +- 프로토타입: 기본 객체를 복사한 뒤 일부만 바꾼다. +- 방문자: 데이터 구조는 유지하고 순회 작업을 밖으로 뺀다. + +--- + +## 프론트엔드 관점 분류 + +| 분류 | 패턴 | 예시 | +| --------------- | ------------------------------------------- | ------------------------------------------------------------- | +| UI 조립 | 컴포지트, 데코레이터, 브리지, 템플릿 메소드 | compound component, HOC, headless UI | +| 상태와 이벤트 | 옵저버, 상태, 커맨드, 책임 연쇄 | store 구독, async state, command palette, validation pipeline | +| API와 외부 연동 | 어댑터, 퍼사드, 프록시, 팩토리 | API mapper, custom hook, API client, mock/real client 교체 | +| 변경 정책 | 전략, 빌더, 프로토타입, 싱글턴 | 정렬 정책, form schema, theme variant, shared cache | + +--- + +## 실무 기준 + +### 컴포넌트 책임 축소 + +```text +결합이 큰 구조: +Page 컴포넌트가 API 응답 구조, 권한 정책, 이벤트 추적, UI 상태를 모두 안다. + +개선 방향: +Page 컴포넌트는 화면 조립에 집중한다. +데이터 변환, 정책 판단, 외부 연동은 함수/훅/어댑터가 맡는다. +``` + +### 서버 모델과 UI 모델 분리 + +```text +Server DTO +→ adapter / mapper +→ View Model +→ Component +``` + +서버 모델은 API 사정에 따라 바뀐다. UI 모델은 화면 요구사항에 맞춘다. + +### 상태 전이 명시 + +로딩, 성공, 실패, 빈 상태, 권한 없음, 재시도가 섞이면 UI 버그가 발생할 수 있다. 상태별 가능한 데이터와 이벤트를 명시한다. + +### 제거 가능한 구조 유지 + +유지보수 가능한 추상화의 기준은 다음과 같다. + +- 호출부가 단순하다. +- 책임이 이름으로 드러난다. +- 테스트 범위가 명확하다. +- 요구사항 변경 시 수정 범위가 줄어든다. +- 필요 없어졌을 때 삭제 범위가 명확하다. + +--- + +## 유의점 + +- React 코드를 GoF 클래스 구조로 억지 변환하지 않는다. +- 함수, 훅, 컴포넌트 조합으로 같은 의도를 표현할 수 있다. +- custom hook을 만능 퍼사드로 만들지 않는다. +- 한 번 등장한 UI 변형만 보고 바로 추상화하지 않는다. +- 패턴 이름만으로 설계를 판단하지 않는다. + +리뷰 시 확인할 질문은 다음과 같다. + +- 어떤 변화 지점을 분리했는가? +- 호출부가 단순해졌는가? +- 새 정책 추가 시 기존 코드 수정이 줄어드는가? +- 테스트 범위가 명확해졌는가? +- 팀원이 구조를 예측 가능하게 읽을 수 있는가? diff --git "a/jihyeon/14.\352\270\260\355\203\200_\355\214\250\355\204\264.md" "b/jihyeon/14.\352\270\260\355\203\200_\355\214\250\355\204\264.md" new file mode 100644 index 0000000..0a86b30 --- /dev/null +++ "b/jihyeon/14.\352\270\260\355\203\200_\355\214\250\355\204\264.md" @@ -0,0 +1,457 @@ +# 14장 기타 패턴: 프론트엔드 관점 정리 + +## 전체 요약 + +| 패턴 | 핵심 | 프론트엔드 해석 | +| ------------ | ----------------------- | ----------------------------------------- | +| 브리지 | 추상화와 구현 분리 | UI 의도와 렌더링/플랫폼 구현 분리 | +| 빌더 | 복잡한 생성 과정 분리 | 쿼리, 스키마, 옵션 생성 규칙 정리 | +| 책임 연쇄 | 요청 처리자 체인 | 검증, 미들웨어, 라우트 가드 체인 | +| 플라이웨이트 | 공유 상태 재사용 | 반복 스타일, 아이콘, 메타데이터 공유 | +| 인터프리터 | 작은 문법 해석 | 필터 DSL, 조건식, 템플릿 평가 | +| 중재자 | 객체 간 의존 조정 | 복잡한 화면 흐름을 hook/controller로 조정 | +| 메멘토 | 상태 저장/복원 | undo/redo, 임시 저장, 편집 히스토리 | +| 프로토타입 | 기존 객체 복제 | 설정, 위젯, 블록, fixture 복제 | +| 비지터 | 데이터 구조와 작업 분리 | AST, 문서 노드, 블록 모델 처리 분리 | + +--- + +## 브리지 패턴 + +### 책 내용 + +브리지 패턴은 **추상화와 구현을 분리하는 패턴**이다. + +책에서는 리모컨과 TV 예제로 설명한다. 리모컨은 조작을 담당하고, TV는 Sony, LG 같은 제조사별 동작을 담당한다. 리모컨 종류와 TV 종류가 함께 늘어나면 상속 조합이 폭발한다. + +```text +RemoteControl ----uses----> TV + ▲ ▲ + | | +SpecialRemote Sony, LG +``` + +핵심은 변화 축이 두 개일 때 하나의 상속 구조에 억지로 넣지 않는 것이다. + +### 프론트엔드 관점 + +UI 의도와 구현 방식이 따로 바뀔 때 유용하다. + +- 같은 차트 API를 SVG, Canvas, WebGL로 렌더링한다. +- 같은 Dialog를 웹, 모바일 웹뷰, 데스크톱에서 다르게 구현한다. +- 같은 결제 UI를 여러 결제 SDK에 연결한다. + +```typescript +interface ChartRenderer { + drawLine(points: Point[]): void; +} + +class LineChart { + constructor(private renderer: ChartRenderer) {} + + render(data: Point[]) { + this.renderer.drawLine(data); + } +} +``` + +### 적용 기준 + +| 적용 | 피함 | +| ----------------------------------- | --------------------------- | +| UI 의도와 구현 플랫폼이 따로 바뀐다 | 단순 variant 분기만 있다 | +| 외부 SDK 구현체가 여러 개다 | props 조합으로 충분하다 | +| 렌더링 방식을 교체해야 한다 | 컴포넌트 내부 문제로 끝난다 | + +--- + +## 빌더 패턴 + +### 책 내용 + +빌더 패턴은 **복잡한 객체 생성 과정을 분리하는 패턴**이다. + +책에서는 집, 피자, 휴가 계획 예제를 든다. Director는 생성 순서를 알고, Builder는 실제 결과물을 만든다. + +```text +HouseDirector + └─ constructHouse(builder) + ├─ buildWalls() + ├─ buildRoof() + └─ buildWindows() +``` + +핵심은 생성 순서와 생성 세부 구현을 분리하는 것이다. + +### 프론트엔드 관점 + +복잡한 설정 객체를 만들 때 유용하다. + +- 검색 API 쿼리 파라미터 +- 차트 옵션 +- 폼 스키마 +- 테이블 컬럼 설정 +- 테스트 fixture + +```typescript +const query = new ProductSearchQueryBuilder() + .keyword("keyboard") + .priceRange(10000, 50000) + .includeReviewStats() + .build(); +``` + +### 적용 기준 + +| 적용 | 피함 | +| --------------------------- | ------------------------- | +| 옵션 조합이 많다 | 객체 리터럴로 충분하다 | +| 기본값과 조건부 필드가 많다 | 생성 규칙이 단순하다 | +| 잘못된 조합을 막아야 한다 | props 몇 개만 넘기면 된다 | + +--- + +## 책임 연쇄 패턴 + +### 책 내용 + +책임 연쇄 패턴은 **요청을 여러 처리자에게 순서대로 넘기는 패턴**이다. + +각 처리자는 요청을 직접 처리하거나 다음 처리자에게 넘긴다. + +```text +Client -> Handler A -> Handler B -> Handler C +``` + +핵심은 요청을 누가 처리할지 클라이언트가 직접 알 필요 없게 하는 것이다. + +### 프론트엔드 관점 + +요청이나 이벤트가 여러 단계를 지나야 할 때 유용하다. + +- 폼 검증 +- fetch middleware +- 라우트 가드 +- 파일 업로드 검사 + +```typescript +type Validator = (value: T) => { ok: true } | { ok: false; message: string }; + +function runValidators(value: T, validators: Validator[]) { + for (const validate of validators) { + const result = validate(value); + if (!result.ok) return result; + } + + return { ok: true }; +} +``` + +### 적용 기준 + +| 적용 | 피함 | +| ----------------------------- | ----------------------- | +| 처리 단계가 순서대로 실행된다 | 처리 순서가 불명확하다 | +| 중간에 요청을 중단할 수 있다 | 단일 함수가 더 명확하다 | +| 검증 규칙을 자주 추가한다 | 디버깅 경로가 숨겨진다 | + +--- + +## 플라이웨이트 패턴 + +### 책 내용 + +플라이웨이트 패턴은 **공유 가능한 상태를 재사용해 메모리 사용을 줄이는 패턴**이다. + +책에서는 나무 예제를 든다. 위치는 나무마다 다르지만, 종류와 이미지 같은 정보는 공유할 수 있다. + +| 구분 | 의미 | 예시 | +| --------- | ----------- | ----------------------- | +| 내부 상태 | 공유 가능 | 나무 종류, 색상, 이미지 | +| 외부 상태 | 객체별 고유 | 좌표, 높이, 선택 여부 | + +핵심은 공유 상태와 개별 상태를 분리하는 것이다. + +### 프론트엔드 관점 + +대량 객체를 다룰 때 유용하다. + +- 지도 마커 +- 캔버스 노드 +- 반복 아이콘과 스타일 +- 문서 편집기 문자 스타일 +- 데이터 그리드 셀 메타데이터 + +```typescript +class MarkerStyleFactory { + private cache = new Map(); + + getStyle(category: string): MarkerStyle { + const cached = this.cache.get(category); + if (cached) return cached; + + const style = createMarkerStyle(category); + this.cache.set(category, style); + return style; + } +} +``` + +### 적용 기준 + +| 적용 | 피함 | +| -------------------------------- | ---------------------- | +| 같은 메타데이터가 대량 반복된다 | 객체 수가 적다 | +| 공유 상태와 개별 상태가 명확하다 | 캐시가 더 복잡하다 | +| 메모리 중복이 문제다 | 렌더 최적화가 본질이다 | + +--- + +## 인터프리터 패턴 + +### 책 내용 + +인터프리터 패턴은 **작은 언어의 문법을 객체 구조로 표현하고 해석하는 패턴**이다. + +`and`, `or`, `equals` 같은 표현식을 객체로 만들고 실행 결과를 계산한다. + +```text +Expression + ├─ TerminalExpression + └─ NonTerminalExpression +``` + +핵심은 반복되는 작은 문법을 안전하게 해석하는 것이다. + +### 프론트엔드 관점 + +사용자 정의 조건을 화면이나 API 요청에 반영할 때 유용하다. + +- 고급 검색 필터 +- 노코드 조건 빌더 +- feature flag 조건식 +- 템플릿 문자열 +- 권한 정책 표현식 + +```typescript +type Expression = + | { type: "equals"; field: string; value: string } + | { type: "gte"; field: string; value: number } + | { type: "and"; expressions: Expression[] }; +``` + +문자열을 `eval`하지 않고 제한된 표현식 모델로 평가한다. + +### 적용 기준 + +| 적용 | 피함 | +| ------------------------------- | ------------------------ | +| 작은 조건 언어가 반복된다 | 단순 조건문으로 충분하다 | +| 사용자가 조건을 조합한다 | 직접 파서 비용이 크다 | +| 표현식을 안전하게 평가해야 한다 | 서버 처리가 더 명확하다 | + +--- + +## 중재자 패턴 + +### 책 내용 + +중재자 패턴은 **객체 간 직접 의존을 중재자에게 모으는 패턴**이다. + +객체들이 서로 직접 호출하면 관계가 복잡해진다. 중재자는 전체 흐름을 한곳에서 조정한다. + +```text +Component A ----\ +Component B ----- Mediator +Component C ----/ +``` + +핵심은 다대다 의존을 중재자 중심 구조로 바꾸는 것이다. + +### 프론트엔드 관점 + +복잡한 화면 흐름을 조정할 때 유용하다. + +- 다단계 회원가입 위저드 +- 필터, 결과 목록, URL 쿼리 동기화 +- 모달, 토스트, 폼, API 상태 조정 +- 여러 입력값이 연결된 계산기 + +```typescript +function useProductSearchMediator() { + const [filters, setFilters] = useState({}); + const [page, setPage] = useState(1); + + function changeFilters(nextFilters: ProductFilters) { + setFilters(nextFilters); + setPage(1); + } + + return { filters, page, changeFilters, changePage: setPage }; +} +``` + +### 적용 기준 + +| 적용 | 피함 | +| ---------------------------------- | -------------------------------- | +| 여러 컴포넌트 상태가 얽힌다 | 단순 parent state로 충분하다 | +| 콜백 연결이 복잡하다 | 전역 스토어에 모두 넣는다 | +| 화면 흐름을 한곳에서 설명해야 한다 | 중재자가 너무 많은 책임을 가진다 | + +--- + +## 메멘토 패턴 + +### 책 내용 + +메멘토 패턴은 **객체 상태를 저장했다가 복원하는 패턴**이다. + +Originator는 상태 스냅샷을 만들고, Caretaker는 스냅샷을 보관한다. Caretaker는 내부 상태를 해석하지 않는다. + +```text +Originator -> creates -> Memento +Caretaker -> stores -> Memento +Originator <- restores from Memento +``` + +핵심은 캡슐화를 유지하면서 상태를 되돌리는 것이다. + +### 프론트엔드 관점 + +사용자가 이전 상태로 돌아가야 하는 UI에 유용하다. + +- 에디터 undo/redo +- 폼 임시 저장 +- 이미지 편집 히스토리 +- 캔버스 변경 이력 +- 필터 조건 되돌리기 + +```typescript +class History { + private past: T[] = []; + + constructor(private present: T) {} + + commit(next: T) { + this.past.push(this.present); + this.present = next; + } +} +``` + +### 적용 기준 + +| 적용 | 피함 | +| ------------------------- | ----------------------- | +| undo/redo가 필요하다 | 상태가 너무 크다 | +| 저장/복원 시점이 명확하다 | 서버 동기화 충돌이 크다 | +| 편집 히스토리가 필요하다 | 단순 reset으로 충분하다 | + +--- + +## 프로토타입 패턴 + +### 책 내용 + +프로토타입 패턴은 **기존 객체를 복제해 새 객체를 만드는 패턴**이다. + +책에서는 몬스터 예제를 든다. `Dragon` 객체를 미리 만들고 필요할 때 복제해 비슷한 객체를 만든다. + +```text +Monster prototype -> clone() -> new Monster +``` + +핵심은 새 객체의 출발점이 클래스가 아니라 기존 객체일 수 있다는 것이다. + +### 프론트엔드 관점 + +기본 구조는 같고 일부 값만 다른 객체를 반복 생성할 때 유용하다. + +- 대시보드 위젯 복제 +- 페이지 빌더 블록 복제 +- 차트 기본 옵션 복제 +- 테스트 fixture 복제 +- 테마 파생 + +```typescript +const cardPrototype = { + type: "card", + layout: { width: 320, height: 180 }, +}; + +function createCard(title: string) { + return { + ...structuredClone(cardPrototype), + id: crypto.randomUUID(), + title, + }; +} +``` + +### 적용 기준 + +| 적용 | 피함 | +| ----------------------------------- | --------------------- | +| 기본 객체에서 일부만 바꾼다 | 생성 규칙이 단순하다 | +| 블록, 위젯, fixture를 반복 생성한다 | 얕은 복사 위험이 크다 | +| 런타임 복제가 자연스럽다 | factory가 더 명확하다 | + +--- + +## 비지터 패턴 + +### 책 내용 + +비지터 패턴은 **데이터 구조와 작업을 분리하는 패턴**이다. + +객체 구조는 유지하고, 출력/검증/통계 같은 작업을 visitor로 추가한다. + +```text +ObjectStructure -> Element.accept(visitor) +Visitor.visitConcreteElementA(element) +Visitor.visitConcreteElementB(element) +``` + +핵심은 데이터 구조가 안정적이고 작업이 자주 늘어날 때 작업을 밖으로 분리하는 것이다. + +### 프론트엔드 관점 + +노드 구조 하나에 여러 작업을 붙일 때 유용하다. + +- 문서 AST 렌더링 +- plain text 추출 +- 접근성 검사 +- 검색 인덱스 생성 +- 노드별 통계 수집 + +```typescript +type DocNode = + | { type: "heading"; text: string } + | { type: "paragraph"; text: string } + | { type: "image"; src: string; alt: string }; +``` + +### 적용 기준 + +| 적용 | 피함 | +| ------------------------------------ | --------------------------------- | +| AST나 문서 노드 구조가 안정적이다 | 노드 타입이 자주 바뀐다 | +| 렌더링, 검증, 직렬화 작업이 늘어난다 | 단순 switch가 더 명확하다 | +| 데이터와 작업 책임을 분리해야 한다 | 일반 React 트리에 억지로 적용한다 | + +--- + +## 프론트엔드 적용 기준 + +| 보이는 문제 | 검토할 패턴 | +| ------------------------------------- | ------------ | +| 변화 축이 두 개 이상이다 | 브리지 | +| 생성 규칙이 복잡하다 | 빌더 | +| 처리 단계가 순서대로 이어진다 | 책임 연쇄 | +| 같은 정보가 대량 반복된다 | 플라이웨이트 | +| 작은 조건 언어를 해석해야 한다 | 인터프리터 | +| 컴포넌트 간 조정이 복잡하다 | 중재자 | +| 이전 상태로 되돌아가야 한다 | 메멘토 | +| 비슷한 객체를 반복 생성한다 | 프로토타입 | +| 같은 데이터 구조에 여러 작업이 붙는다 | 비지터 |