diff --git "a/jihyeon/08.\355\205\234\355\224\214\353\246\277_\353\251\224\354\204\234\353\223\234_\355\214\250\355\204\264.md" "b/jihyeon/08.\355\205\234\355\224\214\353\246\277_\353\251\224\354\204\234\353\223\234_\355\214\250\355\204\264.md" new file mode 100644 index 0000000..60c237e --- /dev/null +++ "b/jihyeon/08.\355\205\234\355\224\214\353\246\277_\353\251\224\354\204\234\353\223\234_\355\214\250\355\204\264.md" @@ -0,0 +1,189 @@ +# **템플릿 메서드 패턴 (Template Method Pattern)** + +알고리즘의 전체 흐름은 부모 클래스가 정의하고, 일부 단계의 구현만 자식 클래스에 맡기는 패턴 +**순서는 부모가 정하고, 세부 동작은 자식이 구현하는 구조** + +### **문제 상황** + +여러 클래스가 비슷한 처리 흐름을 가지지만, 일부 단계만 다를 때가 존재 + +예를 들어 차와 커피는 모두: + +1. 물을 끓이고 +2. 재료를 우려내고 +3. 컵에 따르고 +4. 첨가물을 넣습니다 + +전체 흐름은 비슷하지만, 실제로 우리는 방식과 첨가물은 다르다. + +이걸 각 클래스가 전부 직접 구현하면: + +- 공통 코드가 중복 +- 알고리즘 흐름이 흩어짐 +- 수정할 때 여러 클래스를 함께 고쳐야 함 + +### **해결책** + +공통된 절차는 부모 클래스에 두고, 달라지는 단계만 추상 메서드로 분리 + +```ts +abstract class CaffeineBeverage { + prepareRecipe(): void { + this.boilWater(); + this.brew(); + this.pourInCup(); + this.addCondiments(); + } + + protected abstract brew(): void; + protected abstract addCondiments(): void; + + protected boilWater(): void { + console.log("Boiling water"); + } + + protected pourInCup(): void { + console.log("Pouring into cup"); + } +} +``` + +자식 클래스는 필요한 부분만 구현 + +```ts +class Tea extends CaffeineBeverage { + protected brew(): void { + console.log("Steeping the tea"); + } + + protected addCondiments(): void { + console.log("Adding Lemon"); + } +} + +class Coffee extends CaffeineBeverage { + protected brew(): void { + console.log("Dripping Coffee through filter"); + } + + protected addCondiments(): void { + console.log("Adding Sugar and Milk"); + } +} +``` + +## **핵심 구조** + +- **Template Method**: 전체 순서를 정의하는 메서드 +- **Primitive Operation**: 자식이 구현해야 하는 단계 +- **Hook**: 필요할 때만 선택적으로 오버라이드하는 확장 포인트 + +### **Hook** + +훅은 자식 클래스가 **선택적으로 개입할 수 있는 지점** + +```ts +protected customerWantsCondiments(): boolean { + return true; +} +``` + +자식 클래스는 필요하면 이 메서드를 오버라이드해서 특정 단계를 건너뛸 수 있다. + +## **프론트엔드 개발에서의 사례** + +전통적인 클래스 상속 코드보다, **프레임워크가 전체 흐름을 잡고 개발자가 일부 단계만 구현하는 구조**에서 템플릿 메서드 패턴과 비슷한 개념을 자주 볼 수 있다. + +#### **1. React 컴포넌트 생명주기** + +클래스형 컴포넌트에서는 React가 전체 렌더링 흐름을 관리하고, 개발자는 특정 생명주기 메서드만 구현했습니다. + +```ts +class MyComponent extends React.Component { + componentDidMount() { + console.log("데이터 요청"); + } + + componentWillUnmount() { + console.log("정리 작업"); + } + + render() { + return
Hello
; + } +} +``` + +- React가 전체 생명주기를 통제 +- 개발자는 정해진 시점의 동작만 작성 + +즉, **프레임워크가 흐름을 갖고 있고 사용자가 일부 단계를 채우는 구조**라는 점에서 템플릿 메서드와 닮아 있다. + +#### **2. 폼 처리 흐름** + +폼도 보통 비슷한 흐름 + +1. 입력값 검증 +2. 요청 전처리 +3. API 호출 +4. 성공/실패 후처리 + +제출 절차는 고정하고, 폼마다 검증이나 성공 처리만 다르게 둘 수 있다. + +```ts +abstract class BaseFormHandler { + async submit(values: T) { + if (!this.validate(values)) return; + const payload = this.serialize(values); + await this.request(payload); + this.onSuccess(); + } + + protected abstract validate(values: T): boolean; + protected abstract serialize(values: T): unknown; + protected abstract request(payload: unknown): Promise; + + protected onSuccess() { + console.log("success"); + } +} +``` + +회원가입 폼, 로그인 폼, 프로필 수정 폼이 같은 제출 흐름을 공유할 때 유용하다. + +### **3. 디자인 시스템 컴포넌트 내부 구조** + +디자인 시스템에서도 내부적으로 비슷한 구조가 나올 수 있다. + +예를 들어 여러 입력 컴포넌트가 공통적으로: + +1. 공통 wrapper 렌더링 +2. label 처리 +3. 에러 메시지 처리 +4. 실제 input 요소 렌더링 + +이라는 흐름을 가진다면, 공통 렌더링 구조를 상위 레벨에 두고 실제 input 부분만 바꿀 수 있다. + +- TextField +- PasswordField +- SearchField + +같은 컴포넌트들이 **공통 레이아웃 흐름은 공유하고, 세부 입력 요소만 다르게 구현**하는 방식 + +## **장점** + +- 공통 로직을 재사용할 수 있음 +- 전체 흐름을 부모가 통제할 수 있음 +- 중복 코드가 줄어듦 +- 구조가 일관되어 유지보수가 쉬움 + +## **단점** + +- 상속에 의존함 +- 부모 구조가 바뀌면 자식 클래스에 영향이 큼 +- 단계가 많아지면 클래스 구조가 복잡해질 수 있음 + +## **전략 패턴과의 차이** + +- **템플릿 메서드**: 상속으로 흐름을 재사용, 부모가 순서를 고정 +- **전략 패턴**: 조합으로 동작을 교체, 알고리즘 자체를 바꿔 끼울 수 있다.