본문 바로가기
Delvelopment/DesignPattern

[Design Pattern] 반복자 패턴 (Iterator Pattern)

by 제제킴 2022. 7. 9.
반응형

반복자 패턴(Iterator Pattern)은 컬렉션의 구현 방법을 노출하지 않으면서 집합체 내의 모든 항목에 접근하는 방법을 제공한다.

이 패턴을 사용하면 집합체 내에서 어떤 식으로 일이 처리되는지 전혀 모르는 상태에서 그안에 들어있는 모든 항목을 대상으로 반복 작업을 수행할 수 있습니다. 디자인에 반복자를 적용했을 때 얻는 효과도 중요합니다. 컬렉션 객체 안에 들어있는 모든 항목에 접근하는 방식이 통일되어 있으면 종류에 관계없이 모든 집합체에 사용할 수 있는 다형적인 코드를 만들 수 있기 때문이다. 반복자 패턴을 사용하면 모든 항목에 일일이 접근하는 작업을 컬렉션 객체가 아닌 반복자 객체가 맡게 된다는 사실도 중요하다. 이러면 집합체의 인터페이스와 구현이 간단해지고 집합체는 반복 작업에서 손을 떼고 원래 자신이 할 일에만 전념할 수 있기 때문이다.

  • 반복자패턴을 사용하면 내부 구현 방법을 외부로 노출하지 않으면서 집합체에 있는 모든 항목에 일일이 접근할 수 있습니다.
  • 각 항목에 일일이 접근할 수 있게 해주는 기능을 집합체가 아닌 반복자 객체가 책임진다는 장점도 있다.
  • 즉, 집합체 인터페이스와 구현이 간단해지고, 각자에게 중요한 일만을 처리할 수 있다.

해드퍼스트 디자인패턴

package pattern.iterator;

public record MenuItem(
        String name,
        String description,
        boolean vegetarian,
        double price
) {
}
package pattern.iterator;

public class DinerMenu {

    static final int MAX_ITEMS = 6;
    int numberOfItems = 0;
    MenuItem[] menuItems;

    public DinerMenu() {
        menuItems = new MenuItem[MAX_ITEMS];

        addItem("채식주의자용 BLT",
                "통밀 위에 콩고기 베이컨",
                true,
                2.99);

        addItem("BLT",
                "통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴",
                false,
                2.99);

        addItem("오늘의 스프y",
                "감자 샐러드를 곁들인 오늘의 스프",
                false,
                3.29);

        addItem("핫도그",
                "샤워크라우트, 갖은 양념, 양파, 치즈가 곁들여진 핫도그",
                false,
                3.05);
    }

    public void addItem(String name, String desc, boolean vegetarian, double price) {
        MenuItem item = new MenuItem(name, desc, vegetarian, price);
        if (numberOfItems >= MAX_ITEMS) {
            System.out.println("죄송합니다. 메뉴가 꽉 찼습니다. 더 이상 추가할 수 없습니다.");
        } else {
            menuItems[numberOfItems] = item;
            numberOfItems = numberOfItems + 1;
        }
    }

    public Iterator createIterator() {
        return new DinerMenuIterator(menuItems);
    }

    /*
        내부 구조를 드러내는 단점이 있어 제거.
        public MenuItem[] getMenuItems() {
        return menuItems;
    }*/
}
package pattern.iterator;

import java.util.ArrayList;
import java.util.List;

public class PancakeHouseMenu {

    List<MenuItem> menuItems;

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

        addItem("K&B 팬케이크 세트",
                "스크램블 에그와 토스트가 곁들여진 팬케이크",
                true,
                2.99);

        addItem("레귤러 팬케이크 세트",
                "달걀 프라이와 소시지가 곁들여진 팬케이크",
                false,
                2.99);

        addItem("블루베리 팬케이크",
                "신선한 블루베리와 블루베리 시럽으로 만든 팬케이크",
                true,
                3.49);

        addItem("와플",
                "취향에 따라 블루베리나 딸기를 얹을 수 있는 와플",
                true,
                3.59);
    }

    public void addItem(String name, String desc, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(
                name, desc, vegetarian, price
        );
        menuItems.add(menuItem);
    }

    public Iterator createIterator() {
        return new PancakeHouseIterator(menuItems);
    }

    /*
    내부 구조를 드러내는 단점이 있어 제거.
    public List<MenuItem> getMenuItems() {
        return menuItems;
    }
    */
}

반복을 캡슐화 하기, 즉 반복자 패턴 적용하기.

package pattern.iterator;

public interface Iterator {
    boolean hasNext();

    MenuItem next();
}
package pattern.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 MenuItem next() {
        MenuItem menuItem = items[position];
        position = position + 1;
        return menuItem;
    }
}
package pattern.iterator;

import java.util.List;

public class PancakeHouseIterator implements Iterator {

    List<MenuItem> items;
    int position = 0;

    public PancakeHouseIterator(List<MenuItem> items) {
        this.items = items;
    }

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

    @Override
    public MenuItem next() {
        MenuItem menuItem = items.get(position);
        position = position + 1;
        return menuItem;
    }
}
package pattern.iterator;

public class Waitress {
    PancakeHouseMenu pancakeHouseMenu;
    DinerMenu dinerMenu;

    public Waitress() {
        pancakeHouseMenu = new PancakeHouseMenu();
        dinerMenu = new DinerMenu();
        printMenu();
    }

    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("메뉴\\n ----- \\n 아침메뉴");
        printMenu(pancakeIterator);
        System.out.println("\\n 점심 메뉴");
        printMenu(dinerIterator);
        System.out.println("\\n ---- ");
    }

    private void printMenu(Iterator iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = iterator.next();
            System.out.print(menuItem.name() + ", ");
            System.out.print(menuItem.price() + " -- ");
            System.out.println(menuItem.description());
        }
    }

}

 

  • 인터페이스 개선하기.

import java.util.Iterator;

java util의 Iterator를 활용하여 개선이 가능하다.

단일 역할 원칙, 어떤 클래스에서 맡고 있는 모든 역할은 나중에 코드 변화를 불러올 수 있습니다. 역할이 2개 이상 있으면 바뀔 수 있는 부분이 2개 이상이 되는 것입니다. 이 원칙에 따라 하나의 클래스는 하나의 역할만 맡아야 합니다.

  • 컬렉션이 어떤 이유로 바뀌게 되면 그 클래스도 바뀌어야한다.
  • 반복자 관련 기능이 바뀌었을 때도 클래스가 바뀌어야 한다.
  • 변경과 관련된 디자인 원칙이 추가된다.

응집도(Cohesion)란 한 클래스 또는 모듈이 특정 목적이나 역할을 얼마나 일관된게 지원하는지를 타나태는 척도이다. 어떤 모듈이나 클래스의 응집도가 높다는 것은 서로 연관된 기능이 묶여있다는 것을 의미하며, 응집도가 낮다는 것은 서로 상관 없는 기능들이 묶여있다는 것을 뜻한다.

응집도는 단일 역할 원칙에서만 쓰이는 용어는 아니다. 단일 역할 원칙과 응집도는 서로 밀접하게 연관되어 있다. 이 원칙을 잘 따라는 클래스는 2개 이상의 역할을 맡고 있는 클래스에 비해 응집도가 높고 관리하기도 쉽다.

  • HashMap은 반복자를 간접적으로 지원하는 클래스이다.
  • ArrayList, Vector, LinkedList, Stack, PriorityQueue 등..
  • 리팩토링된 식당.
package pattern.iterator.refact;

public record MenuItem(
        String name,
        String description,
        boolean vegetarian,
        double price
) {
}
package pattern.iterator.refact;

import java.util.Iterator;

public interface Menu {
    Iterator<MenuItem> createIterator();
}
package pattern.iterator.refact;

public interface Iterator<M> {
    boolean hasNext();

    MenuItem next();
}
package pattern.iterator.refact;

import java.util.Iterator;

public class DinerMenu implements Menu {

    static final int MAX_ITEMS = 6;
    int numberOfItems = 0;
    MenuItem[] menuItems;

    public DinerMenu() {
        menuItems = new MenuItem[MAX_ITEMS];

        addItem("채식주의자용 BLT",
                "통밀 위에 콩고기 베이컨",
                true,
                2.99);

        addItem("BLT",
                "통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴",
                false,
                2.99);

        addItem("오늘의 스프y",
                "감자 샐러드를 곁들인 오늘의 스프",
                false,
                3.29);

        addItem("핫도그",
                "샤워크라우트, 갖은 양념, 양파, 치즈가 곁들여진 핫도그",
                false,
                3.05);
    }

    public void addItem(String name, String desc, boolean vegetarian, double price) {
        MenuItem item = new MenuItem(name, desc, vegetarian, price);
        if (numberOfItems >= MAX_ITEMS) {
            System.out.println("죄송합니다. 메뉴가 꽉 찼습니다. 더 이상 추가할 수 없습니다.");
        } else {
            menuItems[numberOfItems] = item;
            numberOfItems = numberOfItems + 1;
        }
    }

    @Override
    public Iterator<MenuItem> createIterator() {
        return new DinerMenuIterator(menuItems);
    }

    /*
        내부 구조를 드러내는 단점이 있어 제거.
        public MenuItem[] getMenuItems() {
        return menuItems;
    }*/
}
package pattern.iterator.refact;

import java.util.Iterator;
import java.util.function.Consumer;

public class DinerMenuIterator implements Iterator<MenuItem> {

    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 MenuItem next() {
        MenuItem menuItem = items[position];
        position = position + 1;
        return menuItem;
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("기능 없음");
    }

    @Override
    public void forEachRemaining(Consumer<? super MenuItem> action) {
        Iterator.super.forEachRemaining(action);
    }
}
package pattern.iterator.refact;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class PancakeHouseMenu implements Menu{

    List<MenuItem> menuItems;

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

        addItem("K&B 팬케이크 세트",
                "스크램블 에그와 토스트가 곁들여진 팬케이크",
                true,
                2.99);

        addItem("레귤러 팬케이크 세트",
                "달걀 프라이와 소시지가 곁들여진 팬케이크",
                false,
                2.99);

        addItem("블루베리 팬케이크",
                "신선한 블루베리와 블루베리 시럽으로 만든 팬케이크",
                true,
                3.49);

        addItem("와플",
                "취향에 따라 블루베리나 딸기를 얹을 수 있는 와플",
                true,
                3.59);
    }

    public void addItem(String name, String desc, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(
                name, desc, vegetarian, price
        );
        menuItems.add(menuItem);
    }

    @Override
    public Iterator<MenuItem> createIterator() {
        return menuItems.stream().iterator();
    }

    /*
    내부 구조를 드러내는 단점이 있어 제거.
    public List<MenuItem> getMenuItems() {
        return menuItems;
    }
    */
}
package pattern.iterator.refact;

import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;

public class PancakeHouseIterator implements Iterator<MenuItem> {

    List<MenuItem> items;
    int position = 0;

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

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

    @Override
    public void remove() {
        throw new UnsupportedOperationException("기능 없음");
    }

    @Override
    public void forEachRemaining(Consumer<? super MenuItem> action) {
        Iterator.super.forEachRemaining(action);
    }
}
package pattern.iterator.refact;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class CafeMenu implements Menu {

    Map<String, MenuItem> menuItems = new HashMap<>();

    public CafeMenu() {
        addItem("베지 버거와 에어 프라이",
                "통밀 위에 콩고기 베이컨",
                true,
                3.99);

        addItem("오늘의 스프",
                "통밀 위에 베이컨, 상추, 토마토를 얹은 메뉴",
                false,
                3.69);

        addItem("부르토",
                "감자 샐러드를 곁들인 오늘의 스프",
                true,
                4.29);

    }

    public void addItem(String name, String desc, boolean vegetarian, double price) {
        MenuItem item = new MenuItem(name, desc, vegetarian, price);
        menuItems.put(name, item);
    }

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

    /*
        내부 구조를 드러내는 단점이 있어 제거.
        public MenuItem[] getMenuItems() {
        return menuItems;
    }*/
}
package pattern.iterator;

public class Waitress {
    PancakeHouseMenu pancakeHouseMenu;
    DinerMenu dinerMenu;

    public Waitress() {
        pancakeHouseMenu = new PancakeHouseMenu();
        dinerMenu = new DinerMenu();
        printMenu();
    }

    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("메뉴\\n ----- \\n 아침메뉴");
        printMenu(pancakeIterator);
        System.out.println("\\n 점심 메뉴");
        printMenu(dinerIterator);
        System.out.println("\\n ---- ");

    }

    private void printMenu(Iterator iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = iterator.next();
            System.out.print(menuItem.name() + ", ");
            System.out.print(menuItem.price() + " -- ");
            System.out.println(menuItem.description());
        }
    }

}

 

반응형

댓글