본문 바로가기

JAVA/Design Patterns

9.이터레이터 패턴

반응형

이터레이터 패턴


정의

-컬렉션 구현방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목에 접근할 수 있게 해주는 방법을 제공해준다.

-> 내부적인 구현방법을 외부로 노출시키지 않으면서도 집합체에 있는 모든 항목에 일일이 접근할 수 있다.

또한 각 항목에 일일이 접근할수 있게 해주는 기능을 집합체가 아닌 반복자 객체에서 책임지게 된다는 것도 장점으로 작용한다. 그러면 집합체 인터페이스 및 구현이 간단해지고 각자 중요한 일만 잘 처리할 수 있으면 된다.


인터페이스 통합

바뀌는 부분을 캡슐화하라.




예)

식당과 팬케이크 하우스 합병


package patterns.iterator;


public class MenuItem {

    String name;

    String description;

    boolean vegetarian;

    double price;


    public MenuItem(String name, String description, boolean vegetarian, double price) {

        this.name = name;

        this.description = description;

        this.vegetarian = vegetarian;

        this.price = price;

    }


    public String getName() {

        return name;

    }


    public String getDescription() {

        return description;

    }


    public boolean isVegetarian() {

        return vegetarian;

    }


    public double getPrice() {

        return price;

    }

}



package patterns.iterator;


import java.util.ArrayList;


public class PancakeHouseMenu {

    ArrayList menuItems;


    public PancakeHouseMenu() {

        menuItems = new ArrayList();


        addItem("K&B 팬케이크 세트",

                "스크램블드 에그와 토스트가 곁들여진 팬케이크",

                true,

                2.99);

        addItem("레귤러 팬케이크 세트",

                "달걀후라이와 소시지가 곁들여진 팬케이크",

                true,

                2.99);

        addItem("블루베리 팬케이크",

                "신선한 블루베리와 블루베리 시럽으로 만든 팬케이크",

                true,

                3.49);

        addItem("와플",

                "와플, 취향에 따라 블루베리나 딸기를 얹을수 있습니다.",

                true,

                3.59);

    }


    private 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 patterns.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("오늘의 스프",

                "감자 샐러드를 곁들인 오늘의 스프",

                false,

                3.29);

        addItem("핫도그",

                "사워크라우트, 갖은 양념, 치즈가 곁들어진 핫도그",

                false,

                3.05);

    }


    private void addItem(String name, String description, boolean vegetarian, double price) {

        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);

        if(numberOfItems >= MAX_ITEMS) {

            System.out.println("죄송합니다. 메뉴가 꽉찼습니다. 더이상 추가할 수 없습니다.");

        } else {

            menuItems[numberOfItems] = menuItem;

            numberOfItems = numberOfItems + 1;

        }

    }


    public MenuItem[] getMenuItems() {

        return menuItems;

    }


}



package patterns.iterator;


import java.util.ArrayList;


public class Waitress {


    public void printMenu() {

        PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();

        ArrayList breakfastItems = pancakeHouseMenu.getMenuItems();


        DinerMenu dinerMenu = new DinerMenu();

        MenuItem[] lunchItems = dinerMenu.getMenuItems();


        for (int i=0; i< breakfastItems.size(); i++) {

            MenuItem menuItem = (MenuItem) breakfastItems.get(i);

            System.out.println(menuItem.getName() + " ");

            System.out.println(menuItem.getPrice() + " ");

            System.out.println(menuItem.getDescription() + " ");

        }


        System.out.println(lunchItems.length);

        for (int i=0; i< lunchItems.length; i++) {

            MenuItem menuItem = lunchItems[i];

            System.out.println(menuItem.getName() + " ");

            System.out.println(menuItem.getPrice() + " ");

            System.out.println(menuItem.getDescription() + " ");

        }

    }


    public static void main(String[] args) {

        Waitress waitress = new Waitress();

        Waitress.printMenu();


    }

}



-인터페이스가 아닌 PancakeHouseMenu와 DinerMenu 구상 클래스에 맞춰서 코딩하고 있다.

-DinerMenu를 사용하는 방식에선 메뉴 항목의 목록을 Hashtable 로 구현한 다른 형식의 메뉴를 사용하는 방식으로 전환하려면 Waitress 코드를 아주 많이 수정해야된다.

-Waitress에서는 각 메뉴에서 메뮤 항목의 컬렉션을 표현하는 방법을 알아야 하므로 캡슐화의 기본원칙이 지켜지지 않고 있다.

- 코드가 중복된다. printMenu()에서 두가지 서로 다른 종류의 메뉴에 들어있는 항목들에 일일이 접근하기 위해 두개의 서로다른 순환문이 필요하다. 그리고 다른 메뉴를 추가로 사용하려면 순환문도 하나 더 추가해야 한다.


바뀌는 부분을 캡슐화하라



package patterns.iterator;


public interface Iterator {

    boolean hasNext();

    Object next();

}



package patterns.iterator;


import java.util.ArrayList;


public class PancakeHouseIterator implements Iterator {

    ArrayList menuItems;

    int position = 0;


    public PancakeHouseIterator(ArrayList menuItems) {

        this.menuItems = menuItems;

    }


    @Override

    public boolean hasNext() {

        if(position >= menuItems.size() || menuItems.get(position) == null) {

            return false;

        } else {

            return true;

        }

    }


    @Override

    public Object next() {

        MenuItem menuItem = (MenuItem) menuItems.get(position);

        position = position + 1;

        return menuItem;

    }

}



package patterns.iterator;


public class DinerMenuIterator implements Iterator {

    MenuItem[] menuItems;

    int position = 0;


    public DinerMenuIterator(MenuItem[] menuItems) {

        this.menuItems = menuItems;

    }


    @Override

    public boolean hasNext() {

        if(position >= menuItems.length || menuItems[position] == null) {

            return false;

        } else {

            return true;

        }

    }


    @Override

    public Object next() {

        MenuItem menuItem = menuItems[position];

        position = position + 1;

        return menuItem;

    }

}



package patterns.iterator;


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("오늘의 스프",

                "감자 샐러드를 곁들인 오늘의 스프",

                false,

                3.29);

        addItem("핫도그",

                "사워크라우트, 갖은 양념, 치즈가 곁들어진 핫도그",

                false,

                3.05);

    }


    private void addItem(String name, String description, boolean vegetarian, double price) {

        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);

        if(numberOfItems >= MAX_ITEMS) {

            System.out.println("죄송합니다. 메뉴가 꽉찼습니다. 더이상 추가할 수 없습니다.");

        } else {

            menuItems[numberOfItems] = menuItem;

            numberOfItems = numberOfItems + 1;

        }

    }

    public Iterator createIterator() {

        return new DinerMenuUtilIterator(menuItems);    //Iterator 인터페이스를 리턴.

    }

}



package patterns.iterator;


import java.util.ArrayList;


public class PancakeHouseIterator implements Iterator {

    ArrayList menuItems;

    int position = 0;


    public PancakeHouseIterator(ArrayList menuItems) {

        this.menuItems = menuItems;

    }


    @Override

    public boolean hasNext() {

        if(position >= menuItems.size() || menuItems.get(position) == null) {

            return false;

        } else {

            return true;

        }

    }


    @Override

    public Object next() {

        MenuItem menuItem = (MenuItem) menuItems.get(position);

        position = position + 1;

        return menuItem;

    }

}



package patterns.iterator;


import java.util.ArrayList;

import java.util.Iterator;


public class PancakeHouseMenu implements Menu {

    ArrayList menuItems;


    public PancakeHouseMenu() {

        menuItems = new ArrayList();


        addItem("K&B 팬케이크 세트",

                "스크램블드 에그와 토스트가 곁들여진 팬케이크",

                true,

                2.99);

        addItem("레귤러 팬케이크 세트",

                "달걀후라이와 소시지가 곁들여진 팬케이크",

                true,

                2.99);

        addItem("블루베리 팬케이크",

                "신선한 블루베리와 블루베리 시럽으로 만든 팬케이크",

                true,

                3.49);

        addItem("와플",

                "와플, 취향에 따라 블루베리나 딸기를 얹을수 있습니다.",

                true,

                3.59);

    }


    private void addItem(String name, String description, boolean vegetarian, double price) {

        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);

        menuItems.add(menuItem);

    }


    public Iterator createIterator() {

        return new PancakeHouseUtilIterator(menuItems);

    }


}



package patterns.iterator;


import java.util.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("메뉴\n-------\n아침메뉴");

        printMenu(pancakeIterator);

        System.out.println("\n점심메뉴");

        printMenu(dinerIterator);


    }


    private void printMenu(Iterator iterator) {

        while (iterator.hasNext()) {

            MenuItem menuItem = (MenuItem) iterator.next();

            System.out.println(menuItem.getName() + ", ");

            System.out.println(menuItem.getPrice() + " -- ");

            System.out.println(menuItem.getDescription());

        }

    }

}



package patterns.iterator;


import java.util.ArrayList;


public class MenuTestDriven {


    public static void main(String[] args) {

        PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();

        DinerMenu dinerMenu = new DinerMenu();

        Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);

        waitress.printMenu();

    }

}



메뉴 구현법이 캡슐화되었다. Waitress 입장에서는 메뉴에서 메뉴 항목의 컬렉션을 어떤식으로 저장하는지 전혀 알수 없다.

-Iterator만 구현한다면 어떤 컬렉션이든 다형성을 활용하여 한 개의 순환문으로 처리할 수 있다.

-Waitress에서 인터페이스만알고 있으면된다.


하지만 Waitress는 여전히 두개의 구상메뉴 클래스에 묶여있다. 수정해야된다.


java.util.Iterator 적용하기


package patterns.iterator;


import java.util.Iterator;


public interface Menu {

    public Iterator createIterator();

}



package patterns.iterator;


import java.util.Iterator;


public class DinerMenuUtilIterator implements Iterator {

    MenuItem[] menuItems;

    int position = 0;


    public DinerMenuUtilIterator(MenuItem[] menuItems) {

        this.menuItems = menuItems;

    }


    @Override

    public boolean hasNext() {

        if(position >= menuItems.length || menuItems[position] == null) {

            return false;

        } else {

            return true;

        }

    }


    @Override

    public Object next() {

        MenuItem menuItem = menuItems[position];

        position = position + 1;

        return menuItem;

    }


    @Override

    public void remove() {

        if(position <= 0) {

            throw new IllegalStateException("next()를 한번도 호출하지 않은 상태에서 삭제할 수 없다.");

        }

        if(menuItems[position - 1] != null) {

            for (int i = position-1; i < menuItems.length -1; i++) {

                menuItems[i] = menuItems[i+1];

            }

            menuItems[menuItems.length-1] = null;

        }

    }

}


package patterns.iterator;


import java.util.ArrayList;

import java.util.Iterator;


public class PancakeHouseMenu implements Menu {

    ArrayList menuItems;


    public PancakeHouseMenu() {

        menuItems = new ArrayList();


        addItem("K&B 팬케이크 세트",

                "스크램블드 에그와 토스트가 곁들여진 팬케이크",

                true,

                2.99);

        addItem("레귤러 팬케이크 세트",

                "달걀후라이와 소시지가 곁들여진 팬케이크",

                true,

                2.99);

        addItem("블루베리 팬케이크",

                "신선한 블루베리와 블루베리 시럽으로 만든 팬케이크",

                true,

                3.49);

        addItem("와플",

                "와플, 취향에 따라 블루베리나 딸기를 얹을수 있습니다.",

                true,

                3.59);

    }


    private void addItem(String name, String description, boolean vegetarian, double price) {

        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);

        menuItems.add(menuItem);

    }


    public Iterator createIterator() {

        return menuItems.iterator();    //ArrayList의 iterator() 메서드로 호출

    }


}





package patterns.iterator;


import java.util.Iterator;


public class Waitress {


    Menu pancakeHouseMenu;  //Menu 인터페이스로 사용

    Menu dinerMenu;



    public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) {

        this.pancakeHouseMenu = pancakeHouseMenu;

        this.dinerMenu = dinerMenu;        

    }



    public void printMenu() {

        Iterator pancakeIterator = pancakeHouseMenu.createIterator();

        Iterator dinerIterator = dinerMenu.createIterator();

        Iterator cateIterator = cafeMenu.createIterator();

        System.out.println("메뉴\n-------\n아침메뉴");

        printMenu(pancakeIterator);

        System.out.println("\n점심메뉴");

        printMenu(dinerIterator);

    }


    private void printMenu(Iterator iterator) {

        while (iterator.hasNext()) {

            MenuItem menuItem = (MenuItem) iterator.next();

            System.out.println(menuItem.getName() + ", ");

            System.out.println(menuItem.getPrice() + " -- ");

            System.out.println(menuItem.getDescription());

        }

    }

}


Waitress 클래스에서 각 메뉴 객체를 참조할때는 구상 클래스 대신 인터페이스를 이용한다.

특정 구현이 아닌 인터페이스에 맞춰서 프로그래밍한다. 원칙을 따르기 때문에 Waitress와 구상클래스 사이의 의존성을 줄일 수 있다.


새로정의한 Menu인터페이스에는 createIterator()라는 메서드 하나만 있다.

PancakeHouseMenu와 DinerMenu에서 모두 이 메서드를 구현한다.

각 메뉴 클래스에서 메뉴 항목을 내부적으로 구현한 방법에 따라 적절한 방식으로 구상 반복자 클래스를 만들어서 리턴할 책임을 가지게 된다.



디자인 원칙

클래스를 바꾸는 이유는 한 가지 뿐이어야 한다. (단일역할원칙)

-> 어떤 클래스에서 맡고 있는 모든 역할들은 나중에 코드 변환을 불러올수 있다.

역할이 두개 이상 있으면 바뀔수 있는 부분이 두가지 이상이 되는것이다.

한 클래스에서는 한가지 역할만 맡도록 해야 한다.


응집도

한클래스  또는 모듈이 특정목적 또는 역할을 얼마나 일관되게 지원하는지를 나타내는 척도.


어떤 모듈 또는 클래스의 응집도가 높다는것은 일련의 서로 연관된 기능이 묶여있다는 뜻.

응집도가 낮다는 것은 서로 상관없는 기능들이 묶여 있다는 뜻.


응집도는 단일원칙에서만 쓰이는 용어는 아니고, 좀더 광범윟한 용도로 쓰이는 용어이다.

하지만 그 둘은 서로 밀접하게 연관되어있다. 이 원칙을 잘 따르는 클래스는 두 개 이상의 역할을 맡고 있는 클래스에 비해 응집도가 높고, 관리하기도 더 용이한 편이다.


카페메뉴 추가시


package patterns.iterator;


import java.util.Hashtable;

import java.util.Iterator;


public class CafeMenu {

    Hashtable menuItems = new Hashtable();


    public CafeMenu() {

        addItem("베지 버거와 에어프라이",

                "통밀빵, 상추, 토마토, 감자튀김이 첨가된 베지버거",

                true,

                3.99);

        addItem("오늘의 스프",

                "샐러드가 곁들여진 오늘의 스프",

                false,

                3.69);

        addItem("베리또",

                "통 핀토콩과 살사, 쿠아카몰이 곁들여진 푸짐한 베리또",

                true,

                4.29);

    }


    private void addItem(String name, String description, boolean vegetarian, double price) {

        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);

        menuItems.put(menuItem.getName(), menuItem);

    }


    public Hashtable getItems(){

        return menuItems;

    }

}


package patterns.iterator;


import java.util.Hashtable;

import java.util.Iterator;


public class CafeMenu implements Menu{

    Hashtable menuItems = new Hashtable();


    public CafeMenu() {

        addItem("베지 버거와 에어프라이",

                "통밀빵, 상추, 토마토, 감자튀김이 첨가된 베지버거",

                true,

                3.99);

        addItem("오늘의 스프",

                "샐러드가 곁들여진 오늘의 스프",

                false,

                3.69);

        addItem("베리또",

                "통 핀토콩과 살사, 쿠아카몰이 곁들여진 푸짐한 베리또",

                true,

                4.29);

    }


    private 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();

        //해시테이블에 들어있는 모든 객체들의 컬렉션을 리턴하는 values()를 호출.

        // 이 컬렉션에서 java.util.Iterator 형식의 객체를 리턴하는 iterator() 메서드 지원

    }


}



package patterns.iterator;


import java.util.Iterator;


public class Waitress {


    Menu pancakeHouseMenu;  //Menu 인터페이스로 사용

    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 cateIterator = cafeMenu.createIterator();

        System.out.println("메뉴\n-------\n아침메뉴");

        printMenu(pancakeIterator);

        System.out.println("\n점심메뉴");

        printMenu(dinerIterator);

        System.out.println("\n저녁메뉴");

        printMenu(cateIterator);

    }


    private void printMenu(Iterator iterator) {

        while (iterator.hasNext()) {

            MenuItem menuItem = (MenuItem) iterator.next();

            System.out.println(menuItem.getName() + ", ");

            System.out.println(menuItem.getPrice() + " -- ");

            System.out.println(menuItem.getDescription());

        }

    }

}


package patterns.iterator;


import java.util.ArrayList;


public class MenuTestDriven {


    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();

    }

}


자바 컬렉션 프레임워크(java collections Framework)에 속하는 클래스

프레임워크는 그냥 클래스와 인터페이스를 모아놓은 것에 불과하다.

ArrayList, Vector, LinkedList, Stack, PriorityQueue 등 모두 이 프레임워크에 속한다.

이 클래스는 모두 java.util.Collection 인터페이스를 구현하는데, 그 인터페이스에는 객체로 구성된 그룹을 조작하기 위한 여러가지 유용한 메서드들이 포함되어 있다.



컬렉션과 반복자를 사용하면 모든 컬렉션 객체에서 자기 자신을 위한 반복자를 리턴할 줄 안다는 장점을 활용할 수 있다. ArrayList의 iterator() 메서드를 호출하면 그 ArrayList를 위해 만들어진 구상 iterator 클래스가 리턴된다. 그 안에서 사용하는 구상 클래스에 대해서는 전혀 관심을 가지기 않아도 된다. 그냥 iterator 인터페이스만 사용하면된다.



다시 Waitress의 반복잡업을 뽑아낸다.



package patterns.iterator;


import java.util.ArrayList;

import java.util.Iterator;


public class Waitress {

    ArrayList menus;


    public Waitress(ArrayList menus) {

        this.menus = menus;

    }


    public void printMenuNew() {

        Iterator menuIterator = menus.iterator();

        while (menuIterator.hasNext()) {

            Menu menu = (Menu) menuIterator.next();

            printMenu(menu.createIterator());

        }

    }


    private void printMenu(Iterator iterator) {

        while (iterator.hasNext()) {

            MenuItem menuItem = (MenuItem) iterator.next();

            System.out.println(menuItem.getName() + ", ");

            System.out.println(menuItem.getPrice() + " -- ");

            System.out.println(menuItem.getDescription());

        }

    }

}



핵심정리

- 반복자를 이용하면 내부 구조를 들어내지 않으면서도 클라이언트로부터 컬렉션 안에 들어있는 모든 원소들에 접근하도록 할 수 있다.

- 이터레이터 패턴을 이용하면 집합체에 대한 반복잡업을 별도의 객체로 캡슐화할 수 있다.

- 이터레이터 패턴을 이용하면 컬렉션에 있는 모든 데이터에 대해서 반복작업을 하는 역할을 컬렉션에서 분리시킬수 있다.

- 이터레이터 패턴을 쓰면 다양한 집합체에 들어있는 객체에 대해 반복작업들에 대해 똑같은 인터페이스를 적용할 수 있기때문에, 집합체에 있는 객체를 활용하는 코드를 만들때 다형성을 활용할 수 있다.

- 한 클래스에서 될 수 있으면 한 가지 역할만 부여하는 것이 좋다.

- 컴포지트 패턴에서는 개별객체와 복합객체를 모두 담아둘수 있는 구조를 제공한다.

- 컴포지트 패턴을 이용하면 클라이언트에서 개별 객체와 복합 객체를 똑같은 방법으로 다룰 수 있다.

- 복합구조에 들어있는 것을 구성요소라고 부른다. 구성요소에는 복합객체와 잎노드가 있다.

- 컴포지트 패턴을 적용할때는 여러가지 장단점을 고려해야한다. 상황에 따라 투명성과 안정성 사이에서 적절한 평형점을 찾아야 한다.


출처 - Head First Design Patterns 저자- 에릭 프리먼, 엘리자베스 프리먼, 케이시 시에라, 버트 베이츠


반응형

'JAVA > Design Patterns' 카테고리의 다른 글

10.스테이트 패턴  (0) 2018.12.17
9.컴포지트 패턴  (0) 2018.12.13
8.템플릿 메서드 패턴  (0) 2018.12.10
7.어댑터 패턴 / 퍼사드 패턴  (0) 2018.12.10
6.커맨드 패턴  (0) 2018.12.06