👀 학습 목표
- 이터레이터 패턴을 이해하며, 사용하는 목적을 이해한다.
- 예제로 이터레이터 패턴을 이해한다.
1. 이터레이터 패턴이란?
컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목을 접근할 수 있게 해준다.
- 아래와 같이 클라이언트는 타입 상관 없이 두 인터페이스로만 접근하면 되므로 구체적인 타입을 알 필요가 없다.
https://refactoring.guru/design-patterns/iterator 이미지 참고
사용 목적
- 클라이언트한테 객체들의 저장 방식을 모두 알려줘야할까요? 아닙니다. 클라이언트는 저장 방식을 알아서 접근하는 것은 불편합니다. 따라서 이터레이터 패턴을 사용해서 어떤 저장 방식이든 같은 방식으로 접근하게 하는 것이 좋습니다.
2. 이터레이터 패턴 적용
2-1. 객체 마을 식당과 객체 마을 팬케이크 하우스 합병
메뉴 데이터를 저장하는 방식이 다른 식당 2개를 합병하려고한다.
- MenuItem
- 우선 해당 클래스로 메뉴를 관리하기로 결정을 하였다.
- DinerMenu: 객체 식당 메뉴 정의 클래스
- PancakeHouseMenu
- 팬케이크 마을 메뉴 정의 클래스
- 이제 이 두 메뉴를 합쳐 보여줄 웨이터인 클라이언트를 구현하자. 구현 기능은 아래와 같다
- printMenu(): 메뉴에 있는 모든 항목 출력
- printBreakfastMenu(): 아침 식사 항목만 출력
- printLunchMenu(): 점심 식사 항목 출력
- printVegerarianMenu(): 채식주의자용 메뉴 항목만 출력
- isItemVegetarian(name): 채식주의자용 인지 확인
- 우선 printMenu 메소드만 구현하자면, 아래와 같다.
- 문제점: 웨이터를 구현하는 것은 불편하고, 코드 관리 확장 등을 하기 힘들어 진다.
2-2. 문제를 해결하기 위해 반복을 캡슐화?
지금까지 원칙 중에 “바뀌는 부분을 캡슐화하라” 가 있었다. 이를 참고해서 반복문을 캡슐화해보자.
웨이터 printMenu 바뀌는 부분 찾기
- breakfaseItems.size() / lunchItems.length
- breakfastItems.get(i) / lunchItems[i]
이터레이터 패턴을 만들어 구현해보자.
- Iterator 인터페이스를 만들어 타입별로 다른 부분을 캡슐화 하자.
- 아래는 다른 식당 2개가 타입이 다른 부분인 next(), hasNext() 메소드를 구현하였다.
코드
- Iterator 인터페이스 정의
- DinerMenuIterator 클래스 정의, 이터레이터 인터페이스 구현
- 리스트 타입
- PancakeHouseMenuIterator 클래스 정의
- ArrayList 타입 구조 정의
모든 메뉴 클래스가 이터레이터를 생성할 수 있는 메소드를 만들자.
- PancakeHouseMenu
- DinerMenu
웨이터 코드 수정
2-3. java.util.literator 적용하기
- PancakeHouseMenu 이터레이터 생성 메소드 수정
- ArrayList 타입인 팬케이크는 이터레이터를 직접 구현할 필요가 없다.
- 이전에 만든 PancakeHouseMenuIterator는 이제 필요없다. 제거하자.
- DinerMenuIterator 에 remove() 메소드 직접 만들기
- 리스트 타입은 Iterator를 직접 구현해야한다.
2-4. 메뉴 인터페이스 통일 시키기
- Menu 인터페이스 만들기
- 두 가지 타입인 메뉴에 “implements Menu” 추가하기
- 웨이터 변경
- Menu 부분 변경
참고
- Head first design patterns 책