Skip to content
Merged
Show file tree
Hide file tree
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
247 changes: 247 additions & 0 deletions jihyeon/09.반복자_패턴_컴포지트_패턴.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
# 반복자 패턴과 컴포지트 패턴

## 1.

반복자 패턴과 컴포지트 패턴은 서로 다른 목적을 가진다.

- **반복자 패턴**은 컬렉션 내부 구조를 드러내지 않고 원소를 순회하기 위한 패턴이다.
- **컴포지트 패턴**은 개별 객체와 복합 객체를 동일한 방식으로 다루기 위한 패턴이다.

이 둘은 별개의 패턴이지만, **계층 구조를 만들고 그 계층 전체를 탐색해야 하는 상황**에서 자연스럽게 함께 사용된다.
Head First Design Patterns의 메뉴 예제가 바로 이 조합을 보여준다.

정리하면 다음과 같다.

- 컴포지트 패턴은 **구조를 통일**한다.
- 반복자 패턴은 **순회를 통일**한다.

---

## 2. 왜 두 패턴을 함께 배울까?

헤드 퍼스트에서는 식당 메뉴 예제를 통해 패턴을 단계적으로 확장한다.

### 2.1 처음 문제: 서로 다른 메뉴를 한 방식으로 순회하고 싶다

팬케이크 하우스 메뉴와 다이너 메뉴는 각각 다른 자료구조를 사용할 수 있다.

- 한 메뉴는 배열을 사용할 수 있고
- 다른 메뉴는 리스트를 사용할 수 있다

하지만 메뉴를 출력하는 쪽에서는 저장 방식보다 **메뉴 항목을 일관되게 순회하는 방법**이 더 중요하다.
이 문제를 해결하기 위해 먼저 반복자 패턴이 도입된다.

### 2.2 다음 문제: 메뉴 안에 또 다른 메뉴가 들어간다

요구사항이 커지면 단순한 메뉴 목록이 아니라 다음과 같은 구조가 필요해진다.

- 전체 메뉴
- 아침 메뉴
- 점심 메뉴
- 디저트 메뉴
- 카페 메뉴

즉, 메뉴 안에 다시 메뉴가 포함되는 **트리 구조**가 된다.
이때는 메뉴 항목 하나와 메뉴 묶음을 같은 방식으로 다루기 위해 컴포지트 패턴이 필요하다.

### 2.3 마지막 문제: 트리 구조 전체를 탐색해야 한다

컴포지트 패턴으로 트리 구조를 만들었다고 끝이 아니다. 이제는 전체 트리를 순회하면서 다음 작업을 해야 한다.

- 전체 메뉴 출력
- 채식 메뉴만 출력
- 특정 조건의 메뉴 검색

이 단계에서 반복자 패턴이 다시 중요해진다.
즉, 헤드 퍼스트에서는 **반복자 → 컴포지트 → 반복자와 컴포지트의 결합**이라는 흐름으로 두 패턴을 연결한다.

---

## 3. 반복자 패턴

### 3.1 정의

반복자 패턴은 컬렉션의 내부 표현을 노출하지 않으면서, 원소에 순차적으로 접근할 수 있는 방법을 제공하는 패턴이다.

> 반복자 패턴은 집합체 객체의 내부 표현을 드러내지 않으면서 그 안의 모든 원소에 순차적으로 접근하는 방법을 제공한다.

### 3.2 문제 상황

서로 다른 자료구조를 사용하는 메뉴들을 하나의 클라이언트 코드에서 출력한다고 가정하자.

```typescript
for (let i = 0; i < breakfastItems.length; i++) {
console.log(breakfastItems[i].getName());
}

for (let i = 0; i < lunchItems.length; i++) {
console.log(lunchItems[i].getName());
}
```

위 방식은 단순해 보이지만 다음과 같은 문제가 있다.

- 클라이언트가 자료구조를 직접 알아야 한다
- 순회 코드가 여러 곳에 중복된다
- 자료구조가 바뀌면 클라이언트도 함께 수정된다
- 컬렉션 캡슐화가 깨진다

### 3.3 해결 방식

컬렉션이 직접 내부 데이터를 노출하는 대신, **반복자 객체**를 반환하게 만든다.

```typescript
interface Iterator<T> {
hasNext(): boolean;
next(): T;
}

interface Menu {
createIterator(): Iterator<MenuItem>;
}
```

이제 클라이언트는 반복자 인터페이스만 알면 된다.

```typescript
const iterator = menu.createIterator();

while (iterator.hasNext()) {
const item = iterator.next();
console.log(item.getName());
}
```

### 3.4 핵심 장점

- 저장 구조와 순회 로직을 분리할 수 있다
- 클라이언트가 자료구조에 의존하지 않는다
- 새로운 컬렉션 타입이 추가되어도 같은 방식으로 다룰 수 있다

---

## 4. 컴포지트 패턴

### 4.1 정의

컴포지트 패턴은 객체를 트리 구조로 구성해 부분-전체 계층을 표현하고, 개별 객체와 복합 객체를 동일하게 다룰 수 있도록 하는 패턴이다.

> 컴포지트 패턴은 객체를 트리 구조로 구성하여 부분-전체 계층을 표현하며, 클라이언트가 개별 객체와 복합 객체를 일관된 방식으로 다룰 수 있게 한다.

### 4.2 문제 상황

메뉴 시스템이 단순 목록이 아니라 계층 구조를 갖는다고 가정하자.

- `MenuItem`: 실제 판매 항목
- `Menu`: 메뉴 항목이나 하위 메뉴를 담는 객체

만약 두 타입을 완전히 다르게 다뤄야 한다면, 클라이언트는 계속 타입을 분기해야 한다.

```typescript
if (component instanceof MenuItem) {
component.print();
} else if (component instanceof Menu) {
for (const child of component.getChildren()) {
child.print();
}
}
```

이 방식은 구조가 깊어질수록 복잡해진다.

- 항목인지 메뉴인지 계속 구분해야 한다
- 재귀 탐색 코드가 클라이언트로 새어나간다
- 출력, 검색, 필터링마다 비슷한 분기문이 반복된다

### 4.3 해결 방식

`MenuItem`과 `Menu`를 공통 상위 타입 `MenuComponent`로 묶는다.

```typescript
abstract class MenuComponent {
add(component: MenuComponent): void {
throw new Error("Unsupported");
}

print(): void {
throw new Error("Unsupported");
}
}
```

- `MenuItem`은 더 이상 내려갈 수 없는 **Leaf**
- `Menu`는 자식을 포함하는 **Composite**

이제 클라이언트는 둘을 구분하지 않고 같은 타입으로 다룰 수 있다.

```typescript
class Waitress {
constructor(private allMenus: MenuComponent) {}

printMenu(): void {
this.allMenus.print();
}
}
```

### 4.4 핵심 장점

- 개별 객체와 복합 객체를 동일하게 다룰 수 있다
- 트리 구조를 자연스럽게 표현할 수 있다
- 클라이언트 코드의 분기를 줄일 수 있다

---

## 5. 두 패턴의 관계

두 패턴은 목적이 다르다.

| 항목 | 반복자 패턴 | 컴포지트 패턴 |
| --------- | ------------------------------------- | ------------------------------------------ |
| 초점 | 순회 방식 통일 | 구조 통일 |
| 해결 문제 | 서로 다른 컬렉션을 같은 방식으로 탐색 | 개별 객체와 그룹 객체를 같은 방식으로 처리 |
| 대표 질문 | "어떻게 순회할 것인가?" | "어떻게 같은 타입으로 다룰 것인가?" |
| 결과 | 클라이언트가 자료구조를 몰라도 됨 | 클라이언트가 객체 종류를 몰라도 됨 |

하지만 실제 설계에서는 자주 함께 쓰인다.

### 5.1 컴포지트 패턴이 구조를 만든다

```text
ALL MENUS
├── PANCAKE HOUSE MENU
│ ├── K&B's Pancake Breakfast
│ └── Regular Pancake Breakfast
├── DINER MENU
│ ├── Vegetarian BLT
│ └── DESSERT MENU
│ ├── Apple Pie
│ └── Cheesecake
└── CAFE MENU
```

이 구조에서는 `Menu`와 `MenuItem`을 모두 `MenuComponent`로 다룰 수 있다.

### 5.2 반복자 패턴이 그 구조를 순회한다

트리 구조가 만들어지면 전체 메뉴를 탐색해야 한다.

```typescript
const iterator = allMenus.createIterator();

while (iterator.hasNext()) {
const component = iterator.next();
if (component.isVegetarian()) {
component.print();
}
}
```

이 코드는 클라이언트가 내부 트리 구조를 몰라도 전체를 탐색할 수 있게 해준다.

### 5.3 핵심 정리

- 컴포지트 패턴은 **부분과 전체를 같은 인터페이스로 묶는다**
- 반복자 패턴은 **그 구조를 외부에 노출하지 않고 순회하게 한다**

즉, 두 패턴은 같이 사용할 때 설계가 더 단순해진다.
Loading