기타/디자인패턴

행위패턴 > 이터레이터 *

ssseung 2021. 2. 11. 15:19

객체를 컬렉션에 저장할 때는 배열, 스택, 해시테이블 등 장단점을 따져 적합한 컬렉션을 선택해 이용한다.

클라이언트에서 모든 객체들에 일일이 접근하는 작업을 하고자할 때, 어떤 식으로 저장했는지 노출하지 않으면서 접근할 수 있도록 하는 방법과 객체들로 구성된 슈퍼 컬렉션에 대해 알아본다.

 

A 식당과 B 식당이 합병하기로 했는데 A 식당은 메뉴를 ArrayList 에 B식당은 배열에 저장한다.

Waitress 는 메뉴를 출력하려면 배열과 리스트 각각의 순환문을 작성해야 한다.

Waitress 가 구상클래스 MenuItem[] 과 ArrayList 에 직접 연결되어있다.

 

public class Waitress {
    PancakeHouseMenu pancake;
    DinerMenu diner;
    
    public Waitress(PancakeHouseMenu pancake,DinerMenu diner){
    	this.pancake=pancake;
    	this.diner=diner;
    }
    
    public void printMenu() {
    
       List<MenuItem> list = pancake.getMenuItems();
       for(MenuItem m : list){
            System.out.println(m.name);
            System.out.println(m.desc);
            System.out.println(m.price);
        }

        MenuItem[] menu = diner.getMenuItems();
        for(int i=0;i<menu.length;i++){
            System.out.println(menu[i].name);
            System.out.println(menu[i].desc);
            System.out.println(menu[i].price);
        }

    }   
}
public class DinerMenu {
    static final int MAX_ITEMS = 6;
    int numOfItems = 0;
    MenuItem [] menu;

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

        addItem("채식 BLT", "콩고기", 2000, true);
        addItem("BLT", "돼지고기", 2000, false);
        addItem("햄버거", "새우", 5000, false);
        addItem("피자", "페퍼로니", 3000, false);
        addItem("파스타", "알리오올리오", 5000, true);
    }

    public void addItem(String name,String desc,double price,boolean vegeterian){
        if(numOfItems>=MAX_ITEMS){
          System.out.println("no more");  
        } else {
            MenuItem m = new MenuItem(name, desc, price, vegeterian);
            menu[numOfItems++] = m;
        }
    }
//배열로 된 메뉴 반환
    public MenuItem [] getMenuItems(){
        return menu;
    }
}

 

다른 컬렉션을 하나의 반복문으로 처리하기 위해 Iterator 인터페이스를 만든다.

// Iterator 직접 만들지 않고 java.util 에 있는 iterator 를 이용해도 무관하다
package iterator;

public interface Iterator {
    public boolean hasNext();
    public Object next();
}
// Concrete 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;
        }
        return true;
    }

    @Override
    public Object next() {
        return (MenuItem)items[position++];
    }    
}
public class DinerMenu {
    static final int MAX_ITEMS = 6;
    int numOfItems = 0;
    MenuItem [] menu;

    public DinerMenu(){   ..    }

    public void addItem(String name,String desc,double price,boolean vegeterian){   ..     }

// 이터레이터 반환으로 수정 
    public Iterator getMenuItems(){
        return new DinerMenuIterator(menu);
    }
}

* Watiress 는 더이상 Diner 와 Pancake 이 어떤 컬렉션으로 메뉴를 저장하고 있는지 알 필요가 없어졌다.

* Watiress 는 오직 Iterator 만 알고 있으며, 컬렉션 내부에 접근하기 위한 반복 작업의 캡슐화 되어있다.

public class Waitress {
    
    PancakeHouseMenu pancakeHouseMenu;
    DinerMenu dinerMenu;

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

    public void printMenu(){
        Iterator pancakeIT = pancakeHouseMenu.getMenuItems();
        Iterator dinerMenuIT = dinerMenu.getMenuItems();
        printMenu(pancakeIT);
        printMenu(dinerMenuIT);
    }

//리스트, 배열 다른 컬렉션 형태를 반복문을 하나로 통합! 
    public void printMenu(Iterator it){
        while(it.hasNext()){
            MenuItem m = (MenuItem) it.next();
            System.out.println(m.name);
            System.out.println(m.desc);
            System.out.println(m.price);
        }
    }
}

 

하지만 아직 두 개의 구상 메뉴 클래스에 의존하고 있다는 문제는 갖고 있다.

두 메뉴 클래스에 getMenuItems 는 각각 Panacke 와 Diner 의 Iterator를 반환하는 공통 메서드이다.

공통 메서드를 가진 Menu 인터페이스를 만들고 Panacke 와 Diner 에서 이를 상속한다.

// Aggregate
public interface Menu {
    public Iterator getMenuItems();
}
// Concrete Aggregate
public class DinerMenu implements Menu {
    static final int MAX_ITEMS = 6;
    int numOfItems = 0;
    MenuItem [] menu;

    public DinerMenu(){ .. }

    public void addItem(String name,String desc,double price,boolean vegeterian){  ..   }

    public Iterator getMenuItems(){
        return new DinerMenuIterator(menu);
    }
}

* 그럼 Waitress 에서 두 개의 구상클래스 대신 한 개의 인터페이스에만 의존할 수 있게 된다.

특정 구현이 아닌 인터페이스에 맞춰 프로그래밍하는 원칙을 지키게 되고 의존성이 줄일 수 있다.

// Client
public class Waitress {
    
    Menu pancakeHouseMenu;
    Menu dinerMenu;

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

    public void printMenu(){   ..  }

    public void printMenu(Iterator it){  ..  }

 
}

 

 

* 반복자를 이용하면 다형적인 코드를 만들 수 있다는 것의 의미

Iterator 를 매개변수로 받는 메서드를 만들면 다형적인 반복작업을 사용한다고 할 수 있다.

Iterator 를 지원하기만 하면 어떤 컬렉션에도 사용 가능한 코드가 만들어지기 때문이다. 컬렉션의 구체적인 구현 방식에는 신경쓰지 않아도 된다.

 

* 집합체 - Diner,Pancake 내에서 내부 컬렉션과 관련된 반복자 메서드를 구현하는 것 

클래스의 역할은 집합체를 관리하는 데 있다.

집합체 관리 와 반복자 메서드는 두 개의 다른 역할이다.

클래스가 수정되는 것은 다른 영향도를 만들 수 있기 때문에 수정을 가능한 줄이는 것이 좋다.

클래스가 컬렉션 뿐만 아니라 반복자 관련 기능도 갖고 있을 때, 클래스는 두 가지 이유로 수정될 수 있다.

만약 새로운 요구사항에 의해 집합체의 컬렉션의 종류가 달라지게 되는 경우, 반복자 관련 기능이 변경되었을 경우.

이는 클래스를 바꾸는 이유는 한가지 뿐이여야 한다는 디자인 원칙에 위반된다.

 

 

출처:

Head First Design Patterns

반응형