Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
311 changes: 311 additions & 0 deletions ryongtai/13.실전_패턴.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
# 13장 실전 디자인 패턴

## 패턴의 정의

<aside>
🔥

단순히 “자주 쓰는 코드 구조”가 아닌,

#### ***”특정 컨텍스트에서 반복적으로 발생하는 문제에 대한 검증된 해결책”***

</aside>

### 1. 컨텍스트

패턴이 적용되는 상황

- 같은 패턴이라도 상황이 다르면 적용 방식이 달라짐.
- 예를 들어, 옵저버 패턴이 GUI 이벤트에서 쓰일 때와 분산 시스템 메시지 큐에서 쓰이는 맥락이 다름

### 2. 반복적 문제

한 번 나타난 문제는 패턴이 아님

- 여러 프로젝트, 여러 도메인에서 반복적으로 나타나는 문제여야 함.
- Rule of Three → 최소 세 번 이상 독립적으로 검증되어야 패턴으로 인정

### 3. 검증된 해결책

단순 아이디어가 아니라, 실전에서 효과가 입증된 해결책

- 패턴은 발명되는 것이 아니라 발견되는 것

---

## 패턴 분류 체계

### 목적에 따른 분류

#### 1. 생성 패턴 (Creational)

- 객체를 어떻게 만드는가
- 생성 과정을 캡슐화해서 시스템이 구체 클래스에 의존하지 않게 함.
- 팩토리 메서드, 추상 팩토리, 싱글턴, 빌더, 프로토타입

#### 2. 구조 패턴 (Structural)

- 객체를 어떻게 조합하는가
- 클래스나 객체를 더 큰 구조로 엮는 방법
- 어댑터, 퍼사드, 데코레이터, 프록시, 컴포지트, 브리지, 플라이웨이트

#### 3. 행동 패턴 (Begavioral)

- 객체가 어떻게 소통하고 책임을 분배하는가
- 알고리즘 객체 간 책임 할당
- 전략, 옵저버, 커맨드, 템플릿 메서드, 반복자, 상태, 비지터, 중재자, 메멘토, 책임 연쇄, 인터프리터

### 범위에 따른 분류

#### 클래스 패턴

- 상속으로 해결
- 컴파일 타임에 결정
- 템플릿 메서드, 팩토리 메서드, 어댑터

#### 객체 패턴

- 컴포지션으로 해결
- 런타임에 변경 가능
- 나머지 대부분

---

## 부록 - 1

### Q1) 클래스 자주 쓰지도 않는데, 범위에 따른 분류를 왜 알아야 함?

#### 클래스 패턴

- 템플릿 메서드 - 상위 클래스가 알고리즘 흐름을 고정, 하위 클래스가 단계를 오버라이드
- 팩토리 메서드 - 상위 클래스가 생성 인터페이스 정의. 하위 클래스가 구체 생성을 결정

→ 상속 계층을 전제, 하지만 FE에서 상속을 쓰는 경우는 거의 없음

#### 메커지늠은 바뀌었지만 문제는 남음

- 템플릿 메서드 → “흐름은 고정하고, 변하는 부분만 교체하고 싶어”

```tsx
// 상속
abstract class DataFetcher {
fetch() {
this.validate() // 고정
this.transform() // 오버라이드
this.save() // 오버라이드
}
}

// 함수형 — 같은 문제, 다른 메커니즘
function useFetchData<T>(
fetcher: () => Promise<T>, // 변하는 부분을 인자로
onSuccess?: (data: T) => void, // 변하는 부분을 인자로
) {
// 흐름은 고정
useEffect(() => {
setLoading(true)
fetcher()
.then(data => { setData(data); onSuccess?.(data) })
.catch(setError)
.finally(() => setLoading(false))
}, [])
}
```

→ 상속이 고차 함수 / 콜백 / 훅의 인자로 대체. 문제와 의도는 동일

- 팩토리 메서드 → “무엇을 생성할지를 하위에서 결정하고 싶음”

```tsx
// 상속
abstract class Dialog {
abstract createButton(): Button
render() {
const btn = this.createButton()
btn.render()
}
}

// 함수형 — 컴포넌트 합성으로 해결
function Dialog({ renderButton }: { renderButton: () => ReactNode }) {
return <div>{renderButton()}</div>
}

// 사용
<Dialog renderButton={() => <PrimaryButton />} />
<Dialog renderButton={() => <IconButton />} />
```

- 상속이 render props / children / 컴포넌트 합성으로 대체

#### 그래서..

| 책에서 말하는 거 | FE는 어떻게..? |
| --- | --- |
| 상속 (extends) | 합성 (Composition) |
| 추상 메서드 오버라이드 | 콜백/함수 인자 |
| 다중 상속 | 변환 함수 |
| 클래스 계층 | 훅 조합 |
| 인터페이스 구현 | TypeScript 타입/제네릭 |

> **“클래스 패턴”이라는 분류 자체를 메커니즘의 분류로 보지 말고, 문제의 분류로 보자!!**
>
- **클래스 패턴** = “컴파일 타임에 구조가 결정되는 문제”
- **객체 패턴** = “런타임에 구조가 바뀌는 문제”

말을 바꿔서 표현하자면,,

- **정적으로 결정되는 것**
- → 컴포넌트 구조, 훅 호출 순서, 타입 정의, 코드 작성 시점에 정해지고 럼타임에 안 바뀜
- **동적으로 바뀌는 것**
- → 전략 교체, 상태 전이, 데코레이터 추가/제거. 런타임에 사용자 행동이나 조건에 따라 달라짐

---

## 패턴 가이드라인

### 1. 패턴을 적용하려고 하지 말자

<aside>
🔥

***“디자인 패턴은 만병통치약이 아님. 패턴을 사용해야 하는 타당한 이유가 있을 때만, 그리고 그 패턴이 자연스럽게 녹아들 때만 사용해야 함.”***

</aside>

**잘못된 접근** : “이 코드에 패턴을 적용할 수 있을까?”

**올바른 접근** : “이 코드에 어떤 문제가 있고, 그 문제를 해결하는 패턴이 있을까?”

### 2. KISS 원칙과의 균형

> **KISS, **‘*Keep It SImple’**
가장 단순한 해결책이 최선이다. 패턴적용이 코드를 더 복잡하게 만든다면 안쓰는게 맞다*
>

패턴이 필요한 경우는 두 가지 뿐

1. 지금 실제로 겪고 있는 문제를 해결할 때

→ “나중에 필요할 수도 있으니까”는 패턴 적용의 근거가 되지 않는다. (YAGNI)

2. 변경이 예상되는 부분을 캡슐화할 때.

→ 단, 이 ‘예상’이 구체적이고 근거가 있어야 함. 막연한 ‘확장성’은 근거가 아님


### 3. 리팩터링과 패턴

> ***“처음부터 패턴을 넣는 것이 아니라, 코드가 성장하면서 필요해질 때 리팩터링으로 도입”***
>

```sql
1단계 : 단순하게 구성 (패턴 없이)
2단계 : 코드가 커지면서 문제 발생 (중복, 분기 폭발, 결합도 증가)
3단계 : 문제를 해결하는 패턴을 식별
4단계 : 리팩터링으로 패턴 도입
```

---

## 공유 어휘로서의 가치 - 커뮤니케이션

- 패턴 이름을 공유하면 대화의 밀도가 높아짐
- 특정 패턴을 지칭하는 어휘에는 그 패턴이 가지는 의도, 구조, 트레이드오프가 다 담김.
- 코드 주석에서도 패턴 이름을 남기면 의도가 명확해짐
- 전제는 팀원 모두가 해당 패턴을 아는 것…

---

## 안티패턴 - 패턴 과잉 (Pattern Fever)

### 체크리스트

- “이 코드에 어떤 패턴을 쓸 수 있을까?” 라고 먼저 생각했는지?
- 패턴 적용 후 코드가 더 길어지고 복잡해졌는데 “더 유연해졌다”고 정당화했는지?
- 팀원이 코드를 이해하려면 패턴 지식이 필요한지?
- 요구사항이 바뀌지 않았는데 “나중을 위해” 패턴을 적용했는지?

### 패턴 남용의 대가

- 파일 수/코드량 불필요하게 증가
- 간접 참조(indirection) 증가로 디버깅이 어려워짐
- 간단한 변경에도 여러 파일을 건드려야 함
- 새로운 팀원의 합류 비용 상승

#### + 간접 참조(indirection)란

A가 B를 직접 호출하는 것이 아니라, 중간에 뭔가를 거치는 것

```tsx
// 직접 참조 — A가 B를 바로 호출
userComponent.fetchUser(id)

// 간접 참조 — A가 C를 거쳐서 B에 도달
userComponent → useUserQuery(훅) → apiClient(퍼사드) → adaptUser(어댑터) → fetch
```

패턴을 적용하면 간접 참조가 늘어남.

각 단계마다 유연성이 생기지만, 동시에 “이 호출이 실제로 뭘 하는지” 추적하려면 단계를 모두 따라가야 함.

간접 참조가 갖는 가치는 결합도를 낮추는 것. A → B 상황에서는 B가 바뀔 때, A도 바뀜. A → C → B면 B가 바뀌어도 A가 알 필요 없음.

하지만 과도한 간접 참조는 코드를 읽기 어렵게 만듬. 유연성을 얻은 대신 가독성을 잃은 것.

```tsx
간접 참조 1~2단계 → 유연성 확보, 추적 가능
간접 참조 3~4단계 → 트레이드오프 구간, 정당한 이유 필요
간접 참조 5단계 이상 → 설계를 다시 봐야 함
```

---

## 패턴보다 원칙

> ***“패턴은 원칙을 구체화한 것이고, 원칙을 이해하면 패턴이 없는 상황에서도 좋은 설계를 할 수 있음”***
>

### 그동안 반복된 원칙들

#### 1. 바뀌는 부분을 캡슐화한다.

→ **전략, 상태, 템플릿 메서드**를 사용하는 이유

#### 2. 상속보다 구성(컴포지션)을 활용한다.

→ **데코레이터, 전략, 어댑터**의 구현 방식

#### 3. 인터페이스에 맞춰 프로그래밍한다.

→ 대부분 패턴의 전제

#### 4. 상호작용하는 객체 사이에서는 가능하면 느슨한 결합을 사용한다.

→ **옵저버**의 핵심

#### 5. 클래스는 확장에 열려 있어야 하지만, 변경에는 닫혀 있어야 한다 (OCP)

→ **데코레이터, 전략**의 설계 근거

#### 6. 추상화된 것에 의존하자, 구상 클래스에 의존하지 말자 (DIP)

→ “**무엇을 하는가”에 의존**하자, **“누가/어떻게 하는가”에 의존**하지 말자.

→ **팩토리, 어댑터**의 설계 근거

#### 7. 최소 지식의 원칙

→ 직접 알 수 있는 것하고만 대화하는 것

```tsx
// 위반 — 깊은 체이닝
response.data.user.profile.settings.theme

// 준수 - 중간에 퍼사드/헬퍼
getUserTheme(response)
```

#### 8. 내부 구현을 바꾸는 이유는 한가지뿐이어야 한다. (SRP)

→ 반복자의 설계 근거