방문자(visitor/iterator) 패턴

Cute_Security15·2024년 4월 21일
0

디자인패턴

목록 보기
1/2
post-thumbnail

상황

필터 드라이버 코드를 보다보면, 이벤트 정보를 저장한 collection (list, hash table, ...) 에
접근할 일이 자주 발생하는데, 필터별로 collection 의 종류가 다른 경우가 많다.

그래서 2가지 문제가 생긴다
1) client 코드에서 이벤트 정보를 사용할때, 필터(aggregate class) 가 어떤 collection 을
쓰는 지, "내부 정보" 를 알아야 한다.

2) client 코드에서 이벤트 정보를 사용할때, 필터 갯수만큼의 반복문을 '작성' 해야 한다.

크게 고려하지 않고 client 코드를 작성하면 보통 이런 형태로 코드가 작성된다.

// client code
public class Waitress {
	Menu pancakeHouseMenu;
	Menu dinerMenu;
	...
	public void printMenu() {
    	// client needs to know which collection they uses internally..
		ArrayList<MenuItem> breakfast = pancakeHouseMenu.getMenuItems();
		MenuItem[] diner = dinerMenu.getMenuItems();
		
		// this will be not good, since number of Menu can be 10x or 100x..
		for (int i=0; i<breakfast.size(); i++) {
			...
		}
		
		for (int i=0; i<diner.length; i++) {
			System.out.println(diner[i].getName() + " ");
            System.out.println(diner[i].getPrice() + " ");
            System.out.println(diner[i].getDescription());
		}
	}
	
}

여기서, collection 을 배열에서 HashMap 로 바꾸면?

  • 필터 코드(DinerMenu) 는 당연히 바뀔 것이고,
  • 필터 코드를 쓰던 client 코드(Waitress) 에서도 제법 큰 수정이 발생할 것이다.

필요한 생각

필터 코드중에서, 내부 collection 에 대한 접근을 지원하는 반복자를 분리해서,
client 에게 리턴하는 방식으로 변경한다.

이때 SRP(단일책임원칙) 을 고려해,
기존 DinerMenu class 에서 구현하지말고, 별도의 반복자 class 에서 구현한다.

  • DinerMenu 클래스는 MenuItem 을 다루는 법을 알고 있다
    (getName, getPrice, getDescription)
  • DinerMenu 클래스가 내부 collection 에 대한 접근을 "알 필요는 없다."
    (hasNext, next, remove)
  • 그래서, DinerMenuIterator 클래스를 만들고, 내부 collection 에 대한 접근을 위임한다.
// iterator code
public class DinerMenuIterator implements Iterator<MenuItem> {
	MenuItem[] items;
	int position = 0;
	
	public DinerMenuIterator(MenuItem[] menus) {
		this.items = menus;		// items.length fixed
	}
	
	// need to implements next, hasNext, remove
	public MenuItem next() {
		MenuItem item = items[position];
		position = position + 1;
		
		return item;
	}
	
	public boolean hasNext() {
		if (position >= items.length || items[position] = null)
			return false;		// null if constructor receive empty menus
		else
			return true;
	}
	
	public void remove() {
		throw new UnsupportedOperationException("cannot delete menu");
	}
}

public class DinerMenu extends Menu {
	MenuItem[] menus;
	...
	// public MenuItem[] getMenuItems() {
	public Iterator<MenuItem> getMenuItems() {
		return new DinerMenuIterator(menus);
	}
}

이렇게 되면,
1) MenuItem 을 다루는 법이 수정될때, DinerMenu 클래스가 바뀌고
2) 내부 collection 에 접근이 바뀔땐, DinerMenuIterator 클래스가 바뀌게 된다.

  • (ex. collection 을 배열에서 hashmap 으로 수정)

얻을수 있는 효과

SRP 를 준수해서 별도의 반복자 class 를 생성하면 얻을수 있는 효과는 아래와 같다.

  • 클래스를 고치는 일을 최소화
  • 연관있는 기능끼리만 묶여있으므로 코드 응집도(cohesion) 가 높아짐
  • 관리하기가 용이해짐

diagram 상 변화

반복자 패턴적용으로 인한 변화를 class diagram 상으로 표현하면 이렇게 될 것이다.

before : client 는 ConcreteMenu 들이 리턴한 collection 에 직접 접근해야 함

after : client 는 Menu 들이 리턴한 iteartor 에게 collection 접근을 위임함.

printMenu 의 변경 가능성

client 가 하는 일은, collection 안에 있는 MenuItem 내용을 출력하는 일이다.

  • MenuItem 만 순차적으로 받을수 있으면 된다.
  • (Iterator 의 next 메소드가 이 역활을 해준다.)

추가 Menu (ex. LunchMenu) 가 생겼을때,
before 구조에선 printMenu 코드가 변경되고, after 구조에선 printMenu 코드가 변경되지 않는다.

  • collection 관리는 Menu 들이 하고 (add)
  • collection 접근은 Iterator 들이 하기 때문 (next, hasNext)
profile
관심분야 : Filesystem, Data structure, user/kernel IPC

1개의 댓글

comment-user-thumbnail
2024년 4월 21일
답글 달기