ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [디자인패턴] 반복자 패턴 (Iterator Pattern)
    IT, 프로그래밍/Design Patterns 2019. 1. 8. 09:41


    반복자 패턴 (이터레이터 패턴, Iterator Pattern) : 컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체안에 들어있는 모든 항목에 접근할 수 있게 해 주는 방법을 제공해 주는 패턴.



    양치기와 염소치기 이야기


    옛날에 양치기와 염소치기가 살았습니다. 아침이 되면 그들은 양과 염소가 밤에 도망갔는지 아니면 늑대의 습격을 받아 희생되었는지 확인하기 위해 숫자를 세었습니다.

    양치기는 훌륭한 목동견의 도움을 받아 양들을 일렬로 쭉 세운다음 한마리 씩 세었습니다.

    반면에 염소치기는 어릴 때 부터 염소들에게 이름을 지어주고 부르면 오도록 훈련을 시켰기 때문에 한 마리씩 이름을 불러서 숫자를 세었습니다.

    어느날 양치기가 너무나 배가 아파서 이웃마을의 의사에게 진찰을 받으러 갔습니다.

    무려 일주일 동안 시간을 비워야 했기 때문에, 오랜 친구인 염소치기에게 양들을 좀 봐달라고 부탁을 했습니다.

    친구가 떠나고, 다시 아침이 되어 염소치기는 숫자를 세려고 하는데 난감한 상황에 빠졌습니다.

    양들을 어떻게 세어야 하는지 친구에게 듣지 못했기 때문입니다.

    결국 그는 일일이 돌아다니면서 양들의 숫자를 세었고, 끝났을 때는 오후가 되었습니다.

    게다가 양치기가 돌아왔을때, 염소치기가 알려준 양의 수 보다 3마리나 부족하였습니다.


    위에서 보듯이 만약 양치기가 아닌 다른사람이, 양에 대해 접근을 하려면 많은 정보를 미리 알아야 합니다. 

    염소치기의 상황은 자바 같은 객체 지향 세계에서 흔하게 겪을 수 있습니다.

    양치기와 염소치기가 양과 염소를 다루는 방식이 다르듯이, 자바의 컬렉션(Collection) 시스템도 다양한 자료구조를 통해 각기 다른 방식으로 데이터를 관리합니다. 

    사용자가 컬렉션을 사용하려고 할 때, 각기 다른 구조들에 대해서 모두 파악하고 있어야 합니다.

    다시 말하면, 캡슐화 되지 않은 반복에 대해서는 사용자가 컬렉션의 구조에 대해서 파악하고 있어야 한다는 것입니다.


    그러나 사용자들은 자료구조를 사용해 편리하고 쉽게 자료를 순회하거나, 특정 자료를 검색하기를 원합니다.

    단순히 데이터를 가져올 때에도 자료구조에 대해 각자 다른 방식으로 접근해야 하는 불편함이 있습니다.


    이럴 필요 없이 흔하게 쓰는 반복기능만 통일해서 쓸 수 있는 기능이 있다면 정말 편해질 겁니다. 


    양치기와 염소치기 이야기의 또 다른 결말

    양치기가 떠난 후에, 염소치기는 어릴적부터 잘 알던 마을의 이장님을 찾아가 양을 세달라고 부탁하였습니다.

    이장님은 어릴적부터 양치기와 염소치기에게 양과 염소를 기르는 방법을 알려 준 분이었습니다.

    양 뿐만 아니라 말 키우는 방법도 아는 경험많고 노련한 분이었죠.

    염소치기는 이장님 덕분에 정확하게 양을 셀 수 있었고 양치기가 돌아온 후에 행복하게 함께 살았습니다. 







    Animal Class

    1
    2
    3
    4
    5
    6
    7
    8
    public interface Animal {
        
        public void eat();
        public void sleep();
        public void sound();
     
     
    }
    cs


    Goat Class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class Goat implements Animal {
     
        
        @Override
        public void eat() {
            System.out.println("짧은 풀 뜯어먹음");
            
        }
     
        @Override
        public void sleep() {
            System.out.println("엎드려서 잠");
            
        }
     
        @Override
        public void sound() {
            System.out.println("음메에에");
            
        }
     
        
        
    }
    cs



    Sheep Class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class Sheep implements Animal {
        
        
        @Override
        public void eat() {
            System.out.println("길쭉한 풀 뜯어먹음");
            
        }
     
        @Override
        public void sleep() {
            System.out.println("앉아서 잠");
            
        }
     
        @Override
        public void sound() {
            System.out.println("메에에");
     
            
        }
     
        
        
    }
    cs



    양과 염소를 관리하는 염소치기와 양치기를 클래스를 만듭니다. 양치기 클래스에는 이터레이터를 만들어주는 함수를 따로 만들어 주어야 합니다. 현재 염소치기는 Map을 가지고 있으므로, 컬렉션에서 기본적으로 이터레이터를 리턴하는 메소드를 가지고 있으므로 따로 이터레이터를 구현하지는 않습니다.


    염소치기 클래스

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
     
    import java.util.HashMap;
    import java.util.Iterator;
     
    public class Goatherd {
     
        public static final int MAX_GOATS = 100;
        private int goatNum = 0;
        public static final HashMap<Integer, Goat> GOATS = new HashMap<>();
     
        public Goatherd() {
            super();
            int i;
     
            for (i = 0; i < 30++i) {
                GOATS.put(i, new Goat());
     
            }
     
            goatNum = i;
     
        }
     
        public void addGoat() {
            
            if (GOATS.size() <= MAX_GOATS) GOATS.put(goatNum++new Goat());
            
     
        }
     
        public void removeGoat() {
     
            GOATS.remove(goatNum--);
     
        }
        
        public Iterator<Integer> createIterator() {
            
            return GOATS.keySet().iterator();
            
        }
     
        
     
        
        
    }
    cs


    염소치기는 이름을 가지고 염소들을 관리합니다. 자바에서는 Map 객체에 대응되겠죠. 기본적으로 자바에서 제공하는 Iterator를 구현하고 있으므로 특별한 이터레이터 객체를 따로 만들지는 않습니다.  


    양치기 클래스

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    import java.util.Iterator;
     
    public class Shepherd {
     
        
        public static final int MAX_SHEEP = 100;
        private int sheepNum = 0;
        public static final  Sheep[] SHEEPS = new Sheep[MAX_SHEEP];
     
        public Shepherd() {
            super();
            int i;
            for (i = 0; i < 30++i) {
                SHEEPS[i] = new Sheep();
     
            }
     
            sheepNum = i;
     
        }
        
        public void addGoat() {
            
            SHEEPS[++sheepNum] = new Sheep(); 
        }
        
        public void removeGoat() {
     
            SHEEPS[sheepNum--= null
     
        }
     
        
        public Iterator<Sheep> createIterator() {
            
            return new SheepIterator(SHEEPS);
            
        }
     
        public int getSheepNum() {
            return sheepNum;
        }
     
        
        
    }
    cs


    양치기는 배열을 통해 양들을 관리하기 때문에 따로 이터레이터 클래스를 만들어 주어야 합니다. 이 역시 자바의 이터레이터 인터페이스를 사용합니다.


    양을 세는 이터레이터를 구현하는 클래스

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    import java.util.Iterator;
     
    public class SheepIterator implements Iterator<Sheep> {
     
        private Sheep[] sheeps;
        int position = 0;
     
        public SheepIterator(Sheep[] sheeps) {
            super();
            this.sheeps = sheeps;
     
        }
     
        @Override
        public boolean hasNext() {
            if (position >= sheeps.length || sheeps[position] == null) {
                return false;
     
            } else {
                return true;
            }
        }
     
        @Override
        public Sheep next() {
            Sheep tempSheep = sheeps[position];
            position++;
            return tempSheep;
        }
     
    }
    cs



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public static void main(String[] args) {
            
            Goatherd goatherd = new Goatherd();
            Shepherd shepherd = new Shepherd();
            
            Iterator<Integer> goatIter = goatherd.createIterator();
            Iterator<Sheep> sheepIter = shepherd.createIterator();
            
            while(goatIter.hasNext()) {
                
                Goat goat = goatherd.GOATS.get(goatIter.next());
                goat.sound();
            }
            
            
            
            while(sheepIter.hasNext()) {
                
                Sheep sheep = sheepIter.next();
                sheep.sound();
            }
            
     
        }
    cs


    염소치기와 양치기가 모두 거의 동일한 방식으로 서로의 집합 객체들에게 접근이 가능합니다.




    전체적인 클래스 다이어그램입니다. Goat는 자바의 기본적은 컬렉션 객체를 사용하므로 위의 클래스 다이어그램에는 나오지 않지만, SheepIterator와 Goat Map을 순회하는 Iterator 모두 Iterator<E> 인터페이스를 구현하고 있습니다.


    양을 관리하는 집합객체인 양치기 객체에는 SheepIterator를 반환하는 createIterator() 메소드를 통해 이터레이터를 반환 받을 수 있습니다.




    정리 ! 반복자 패턴(Iterator Pattern)은 모든 집합체에 대하여 동일한 인터페이스로 모든 원소를 순회할 수 있도록 해준다.

    반복자 패턴을 사용하기 위해서는 Iterator 인터페이스를 구현한 클래스를 만들어서 해당 자료구조나 집합체가 가진 모든 원소를 순회할 수 있는 기능을 정의해야 하며,

    사용할 집합체 안에 이터레이터를 만드는 메소드를 제공하여야 한다. (양치기 이터레이터, Java의 Map 이터레이터..)




    실제 사용 예제



    아래는 ArrayList 클래스를 확인한 결과입니다.





    모든 컬렉션 객체들이 상속받는 Collection 인터페이스에서 Iterable 인터페이스를 상속받는것을 확인할 수 있습니다.



    이 인터페이스에 따라 집합체인 ArrayList는 이터레이터를 반환하는 함수를 정의합니다.




    실제 ArrayList에서 구현하는 Iterator 입니다.




    자바에서 컬렉션 인터페이스를 구현하는 모든 객체는 Iterator를 구현하고 있으므로 사용법을 꼭 익히시기를 바랍니다.



    ++ 자바의 foreach문 또한 이터레이터를 사용합니다. 내부적으로는 이터레이터를 통해 반복을 하는거죠. 그렇기에 Iterable 인터페이스가 구현되지 않는 집합체는 사용할 수가 없습니다!






Designed by Tistory.