본문 바로가기
컴퓨터 과학/디자인패턴

[디자인패턴] 행위 패턴(4) : 반복자(iterator)

by webcodur 2024. 3. 25.
728x90

목차

     

     

    반복자(iterator)

    반복자(Iterator) 디자인 패턴은 객체 지향 프로그래밍에서 컬렉션 내의 요소들에 순차적으로 접근할 수 있는 방법을 제공한다. 이 패턴의 명칭은 '반복'이라는 뜻의 'Iterate'에서 유래했다. 컬렉션의 구현 방법을 알 필요 없이 그 내용을 순회할 수 있게 해주며, 이는 컬렉션의 구조 변경이 클라이언트 코드에 영향을 미치지 않도록 한다. 기본적인 구성요소로는 IteratorAggregate 인터페이스가 있으며, Iterator 인터페이스는 순회 로직을, Aggregate 인터페이스는 반복자 객체를 생성하는 메서드를 정의한다.

     

    1. 패턴 미적용 예시

    게임에서 여러 몬스터를 관리하는 경우를 생각해보자. 패턴을 적용하지 않았을 때는 몬스터 리스트를 직접 순회하며 각 몬스터에 대한 작업을 수행해야 한다.

    using System;
    using System.Collections.Generic;
    
    class Monster {
        public string Name { get; set; }
        public Monster(string name) {
            Name = name;
        }
    }
    
    class Game {
        private List<Monster> monsters = new List<Monster>();
    
        public void AddMonster(Monster monster) {
            monsters.Add(monster);
        }
    
        public void ShowMonsters() {
            foreach (Monster monster in monsters) {
                Console.WriteLine(monster.Name);
            }
        }
    }
    
    class Program {
        static void Main(string[] args) {
            Game game = new Game();
            game.AddMonster(new Monster("드래곤"));
            game.AddMonster(new Monster("고블린"));
            game.ShowMonsters();
        }
    }

     

    이 코드에서 Game 클래스는 몬스터 목록에 대한 직접적인 접근과 순회를 관리한다. 몬스터 컬렉션의 구현이 변경될 경우, ShowMonsters 메서드도 함께 변경해야 한다는 문제가 있다.

     

     

    2. 반복자 패턴 적용 예시

    반복자 패턴을 적용하면 컬렉션의 구현 변경이 클라이언트 코드에 영향을 미치지 않도록 할 수 있다.

    using System;
    using System.Collections;
    using System.Collections.Generic;
    
    // Iterator 인터페이스 정의
    interface IIterator {
        bool HasNext();
        object Next();
    }
    
    // Aggregate 인터페이스 정의
    interface IAggregate {
        IIterator CreateIterator();
    }
    
    // ConcreteIterator 구현
    class MonsterIterator : IIterator {
        private List<Monster> monsters;
        private int position = 0;
    
        public MonsterIterator(List<Monster> monsters) {
            this.monsters = monsters;
        }
    
        public bool HasNext() {
            return position < monsters.Count;
        }
    
        public object Next() {
            if (this.HasNext()) {
                return monsters[position++];
                // 컬렉션의 다음 요소를 반환하고, 그 후에 내부적인 순회 위치(인덱스)를 하나 증가
            } else {
                return null;
            }
        }
    }
    
    // ConcreteAggregate 구현
    class Game : IAggregate {
        private List<Monster> monsters = new List<Monster>();
    
        public void AddMonster(Monster monster) {
            monsters.Add(monster);
        }
    
        public IIterator CreateIterator() {
            return new MonsterIterator(monsters);
        }
    }
    
    class Program {
        static void Main(string[] args) {
            Game game = new Game();
            game.AddMonster(new Monster("드래곤"));
            game.AddMonster(new Monster("고블린"));
    
            IIterator iterator = game.CreateIterator();
            while (iterator.HasNext()) {
                Monster monster = (Monster)iterator.Next();
                Console.WriteLine(monster.Name);
            }
        }
    }

     

     

    이 예시에서는 MonsterIterator 클래스가 몬스터 리스트를 순회하는 로직을 캡슐화한다. Game 클래스는 IAggregate 인터페이스를 구현하며 CreateIterator 메서드를 통해 반복자 인스턴스를 생성한다. 클라이언트 코드(Program 클래스)는 Game 객체의 CreateIterator 메서드를 사용하여 몬스터 컬렉션을 순회하는 **Iterator**를 얻는다. 이렇게 함으로써, 몬스터 컬렉션의 내부 구현이 변경되어도, 클라이언트 코드는 영향을 받지 않게 된다. 컬렉션의 구조가 변경되더라도 MonsterIterator 만 적절히 수정하면, 나머지 코드는 변경할 필요가 없다.

     

    이 패턴의 적용으로 얻을 수 있는 이점은 다음과 같다:

    • 추상화된 순회: 클라이언트는 컬렉션의 구현 세부 사항을 알 필요 없이 순회할 수 있다.
    • 단일 책임 원칙(Single Responsibility Principle): 컬렉션의 관리와 순회 로직이 분리되어, 각각의 클래스가 단일 책임을 가진다.
    • 확장성: 새로운 컬렉션 타입이나 순회 방식을 추가하기 용이하다. 기존 코드를 변경하지 않고 새로운 반복자를 구현함으로써 확장할 수 있다.

    반복자 디자인 패턴을 사용함으로써, 컬렉션의 내부 구현 변경에 대한 유연성을 확보하고, 순회 로직의 재사용성을 높일 수 있다. 특히, 게임 개발과 같은 분야에서는 다양한 타입의 요소들을 효과적으로 관리하고 순회할 수 있는 방법이 필요하므로, 반복자 패턴은 매우 유용한 디자인 선택이 될 수 있다.