이터레이터 패턴

강한친구·2022년 4월 14일
0

OOP Desing Pattern

목록 보기
12/15

서로 다른 식당 메뉴

package iterator;

public class MenuItem {
    String name;
    String description;
    boolean vegetarian;
    double price;

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    public boolean isVegetarian() {
        return vegetarian;
    }

    public double getPrice() {
        return price;
    }

    public MenuItem(String name, String description, boolean vegetarian, double price) {
        this.name = name;
        this.description = description;
        this.vegetarian = vegetarian;
        this.price = price;
    }
}

이러한 클래스에 의거, 메뉴를 만든 식당이 두개 있다고 하자.

package iterator;

import java.util.ArrayList;

public class PancakeHouseMenu {
    ArrayList menuItems;

    public PancakeHouseMenu() {
        menuItems = new ArrayList();

        addItem("K&B Pancake Set", "Scrambled egg and Toast w/ pancake", true, 2.99);
        addItem("Regular Pancake Set", "Scrambled egg and Sausage w/ pancake", false, 2.99);
        addItem("BlueBerry Pancake Set", "BlueBerry and Syrup pancake", true, 3.49);
        addItem("Waffle", "Choose Your Favourite Syrup", true, 3.59);

    }
    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        menuItems.add(menuItem);
    }
    public ArrayList getMenuItems() {
        return menuItems;
    }
}
package iterator;

import java.util.ArrayList;

public class DinerMenu {
    static final int MAX_ITEM = 6;
    int numberOfItems = 0;
    MenuItem[] menuItems;

    public DinerMenu() {
        menuItems = new MenuItem[MAX_ITEM];
        addItem("Veggie BLT", "BLT for Vegetarian", true, 2.99);
        addItem("BLT", "BLT for MeatLover", true, 2.99);
        addItem("Today's Soup", "Mushroom Potato Soup", true, 3.29);
        addItem("HotDog", "Isotope HotDog", true, 3.05);
    }
    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        if (numberOfItems >= MAX_ITEM) {
            System.out.println("Menu is full");
        } else {
            menuItems[numberOfItems] = menuItem;
            numberOfItems++;
        }
    }
    public MenuItem[] getMenuItems() {
        return menuItems;
    }
}

팬케이크 식당은 ArrayList를 사용했고, Diner식당은 array를 사용했다. 이 두 식당을 합병하려면 어떻게 해야할까?

굳이 방법을 찾자면 waitress 클래스에서 새로운 리스트를 만들고 양쪽의 내용물을 for 문으로 돌면서 전부 꺼내서 새로운 리스트에 넣는것이다.

근데 이러한 방식을 쓸 경우, 캡슐화가 되어있지도 않으며, 코드도 중복되고, 구현에 의존하는 코딩이 된다.

반복의 캡슐화

Iterator 패턴과 어댑터를 통해 반복을 캡슐화 할 수 있다.

package iterator;

public interface Iterator {
    boolean hasNext();
    Object next();
}
package iterator;

public class DinerMenuIterator implements Iterator{
    MenuItem[] items;
    int position = 0;

    public DinerMenuIterator(MenuItem[] items) {
        this.items = items;
    }

    @Override
    public boolean hasNext() {
        if (position >= items.length || items[position] == null) {
            return false;
        } else {
            return true;
        }
    }

    @Override
    public Object next() {
        MenuItem menuitem = items[position];
        position++;
        return menuitem;
    }
}

iterator 인터페이스를 받아서 이를 구현할 수 있다.
이 구현한 클래스를 이제 DinerMenu에 적용하면 된다.

package iterator;

import java.util.ArrayList;

public class DinerMenu {
    static final int MAX_ITEM = 6;
    int numberOfItems = 0;
    MenuItem[] menuItems;

    public DinerMenu() {
    }
    public void addItem(String name, String description, boolean vegetarian, double price) {
    }
    public Iterator createIterator() {
        return new DinerMenuIterator(menuItems);
    }
}

이런식으로 get 부분을 iterator로 바꾸고 처리할 수 있다.

마찬가지로 ArrayList를 쓴 것도 처리가 가능하다.

package iterator;

import java.util.ArrayList;

public class PanCakeHouseMenuIterator implements Iterator{
    ArrayList<MenuItem> items;
    int position = 0;

    public PanCakeHouseMenuIterator(ArrayList items) {
        this.items = items;
    }

    @Override
    public boolean hasNext() {
        if (position >= items.size() || items.get(position) == null) {
            return false;
        } else {
            return true;
        }
    }

    @Override
    public Object next() {
        MenuItem menuitem = items.get(position);
        position++;
        return menuitem;
    }
}

메뉴 출력

package iterator;

public class Waitress {
    PancakeHouseMenu pancakeHouseMenu;
    DinerMenu dinerMenu;

    public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu) {
        this.pancakeHouseMenu = pancakeHouseMenu;
        this.dinerMenu = dinerMenu;
    }

    public void printMenu() {
        Iterator pancakeIterator = pancakeHouseMenu.createIterator();
        Iterator dinerIterator = dinerMenu.createIterator();
        System.out.println("Menu\n---\nMorning Menu");
        printMenu(pancakeIterator);
        System.out.println("\nLunch Menu");
        printMenu(dinerIterator);
    }
    private void printMenu(Iterator iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = (MenuItem)iterator.next();
            System.out.print(menuItem.getName() + ", ");
            System.out.print(menuItem.getDescription() + " -- ");
            System.out.println(menuItem.getPrice());
        }
    }
}

각 메뉴객체에서 이터레이터를 만들고, 이 이터레이터의 hasNext기능을 통해서 하나씩 빼가면서 출력을 할 수 있다.

package iterator;

public class MenuTestDriver {
    public static void main(String[] args) {
        PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
        DinerMenu dinerMenu = new DinerMenu();

        Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);
        waitress.printMenu();
    }
}

드라이버 코드를 돌려보면

Menu
---
Morning Menu
K&B Pancake Set, Scrambled egg and Toast w/ pancake -- 2.99
Regular Pancake Set, Scrambled egg and Sausage w/ pancake -- 2.99
BlueBerry Pancake Set, BlueBerry and Syrup pancake -- 3.49
Waffle, Choose Your Favourite Syrup -- 3.59

Lunch Menu
Veggie BLT, BLT for Vegetarian -- 2.99
BLT, BLT for MeatLover -- 2.99
Today's Soup, Mushroom Potato Soup -- 3.29
HotDog, Isotope HotDog -- 3.05

Process finished with exit code 0

메뉴판이 예쁘게 나온다.

정리

이러한 기능을 통해서 우리가 얻은것은

  1. 이터레이터를 따로 구현해서 이쪽이 반복문 처리를 해준다. 따라서 각 메뉴는 그냥 가져다 쓰기만하고 (createIterator) 세부적인 내용은 몰라도 된다.

  2. 반복문을 한번만 써도 된다!

  3. 각자 다른 자료형이여도 통일이 가능하다. (심지어 메뉴 클래스가 달라도 구현가능하다)

  4. 가능하면 통일해서 쓰자...

java.util.Iterator 쓰기

    public Iterator createIterator() {
        return menuItems.iterator();
    }

panCake의 경우 이미 ArrayList를 사용하고 있기때문에, 이렇게 createIterator 부분만 변경해주면 된다.

public void remove() {
        if (position <= 0) {
            throw new IllegalStateException();
        }
        if (list[position-1] != null) {
            for(int i = position - 1; i < (list.length - 1); i++) {
                list[i] = list[i + 1];
            }
            list[list.length - 1] = null;
        }

DinerMenu의 경우 array를 사용하고있었고 이를 처리하기위해 DinerMenuIterator라는 별도의 클래스를 사용하고 있었다. 따라서 이 클래스의 util.Iterator가 가지고있는 remove를 추가로 구현해주어야한다.

package composite;

import java.util.Iterator;

public interface Menu {
    public Iterator createIterator();
}

그리고 이를 통합관리하는 Menu Interface를 만들면 된다.

새로운 메뉴 추가

package composite;

import java.util.Hashtable;
import java.util.Iterator;

public class CafeMenu implements Menu{
    Hashtable menuItems = new Hashtable();

    public CafeMenu() {
        addItem("Veggie burger and Air-fried chips", "Veggie burger for veggies", true, 3.99);
        addItem("Today's Soup", "Random Soup", false, 3.69);
        addItem("Burrito", "Salsa, Guacamole", true, 4.29);
    }

    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
        menuItems.put(menuItem.getName(), menuItem); // 이름을 키로 쓴다
    }

    @Override
    public Iterator createIterator() {
        return menuItems.values().iterator();
    }
}

새로운 메뉴가 추가된다 하더라도 이런식으로 메뉴 클래스를 작성하고 (이번경우는 해시테이블을 사용한다.)

똑같이 createIterator해주면 된다.

public class Waitress {
    Menu pancakeHouseMenu;
    Menu dinerMenu;
    Menu cafeMenu;

    public Waitress(Menu pancakeHouseMenu, Menu dinerMenu, Menu cafeMenu) {
        this.pancakeHouseMenu = pancakeHouseMenu;
        this.dinerMenu = dinerMenu;
        this.cafeMenu = cafeMenu;
    }

    public void printMenu() {
        Iterator pancakeIterator = pancakeHouseMenu.createIterator();
        Iterator dinerIterator = dinerMenu.createIterator();
        Iterator cafeIterator = cafeMenu.createIterator();
package composite;

public class MenuTestDriver {
    public static void main(String[] args) {
        PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
        DinerMenu dinerMenu = new DinerMenu();
        CafeMenu cafeMenu = new CafeMenu();

        Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu, cafeMenu);
        waitress.printMenu();
    }
}

이제 어떤 자료형을 쓰는 클래스가 와도 반복문 하나로 모두 출력할 수 있다.

자바 프레임워크 컬렉션

프레임워크 컬렉션은 그냥 클래스와 인터페이스를 모아둔 것이다.
ArrayList, Vector, Stack, PQ 같은것들이 전부 모여있다. 이 프레임워크 안에서 Iterator를 사용하면, 안에 있는 인터페이스에만 의존하는 구조가 완성된다.

0개의 댓글