diff --git "a/ryongtai/13.\354\213\244\354\240\204_\355\214\250\355\204\264.md" "b/ryongtai/13.\354\213\244\354\240\204_\355\214\250\355\204\264.md" new file mode 100644 index 0000000..245d3cf --- /dev/null +++ "b/ryongtai/13.\354\213\244\354\240\204_\355\214\250\355\204\264.md" @@ -0,0 +1,311 @@ +# 13장 실전 디자인 패턴 + +## 패턴의 정의 + + + +### 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( + fetcher: () => Promise, // 변하는 부분을 인자로 + 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
{renderButton()}
+ } + + // 사용 + } /> + } /> + ``` + + - 상속이 render props / children / 컴포넌트 합성으로 대체 + +#### 그래서.. + +| 책에서 말하는 거 | FE는 어떻게..? | +| --- | --- | +| 상속 (extends) | 합성 (Composition) | +| 추상 메서드 오버라이드 | 콜백/함수 인자 | +| 다중 상속 | 변환 함수 | +| 클래스 계층 | 훅 조합 | +| 인터페이스 구현 | TypeScript 타입/제네릭 | + +> **“클래스 패턴”이라는 분류 자체를 메커니즘의 분류로 보지 말고, 문제의 분류로 보자!!** +> +- **클래스 패턴** = “컴파일 타임에 구조가 결정되는 문제” +- **객체 패턴** = “런타임에 구조가 바뀌는 문제” + +말을 바꿔서 표현하자면,, + +- **정적으로 결정되는 것** + - → 컴포넌트 구조, 훅 호출 순서, 타입 정의, 코드 작성 시점에 정해지고 럼타임에 안 바뀜 +- **동적으로 바뀌는 것** + - → 전략 교체, 상태 전이, 데코레이터 추가/제거. 런타임에 사용자 행동이나 조건에 따라 달라짐 + +--- + +## 패턴 가이드라인 + +### 1. 패턴을 적용하려고 하지 말자 + + + +**잘못된 접근** : “이 코드에 패턴을 적용할 수 있을까?” + +**올바른 접근** : “이 코드에 어떤 문제가 있고, 그 문제를 해결하는 패턴이 있을까?” + +### 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) + +→ 반복자의 설계 근거 \ No newline at end of file