Iterator 패턴

2020-06-01

Iterator 패턴

Iterator 패턴의 목적은 다음과 같습니다.

“구현의 노출없이 Collection 객체의 엘리먼트에 접근할 수 있는 방법을 제공해준다.”

Iterator 패턴은 collection을 순회하는 것 뿐만 아니라, 요구사항에 따라 다른 종류의 iterator를 제공해줄 수 있습니다.

Iterator 패턴은 collection을 순회하는 실제 구현체를 숨기기 때문에, 클라이언트에서는 그저 iterator 메소드만 이용합니다.

Iterator 패턴의 예제

간단한 예제로 Iterator 패턴이 뭔지 알아보겠습니다. 여러 개 라디오 채널과 그 채널을 하나씩 채널의 언어 타입에 따라 순회하는 클라이언트 프로그램이 있다고 가정해보겠습니다. 예를 들어 몇몇 클라이언트 프로그램은 English로 된 라디오 채널에 관심이 있을 것이고, 다른 언어로 된 라디오 채널 타입엔 관심이 없을 수 있습니다.

그래서 라디오의 채널 Collection을 클라이언트에게 제공해주고, 클라이언트에서 채널을 순회하는 로직과 처리하는 방법을 클라이언트에서 작성하게 됩니다. 그러나 이 해결법은 클라이언트가 순회하는 방법을 생각해내야된다는 이슈가 있습니다. 게다가 클라이언트의 수가 많아질수록 유지보수도 하기 어려워 집니다.

그래서 이번 이럴 때 iterator 패턴을 사용해서 채널 타입에 따라 순회방법을 제공해주어, 정해준 iterator 방법에 따라 클라이언트가 Collection을 순회할 수 있게 됩니다.

Interator 패턴 구현

ChannelTypeEnum.java

public enum ChannelTypeEnum {
    ENGLISH, HINDI, FRENCH, KOREAN, ALL;
}

ChannelTypeEnum은 채널의 언어 종류를 정의해 놓은 enum 클래스 입니다.

Channel.java

public class Channel {

    private double frequency;
    private ChannelTypeEnum TYPE;

    public Channel(double freq, ChannelTypeEnum type){
        this.frequency=freq;
        this.TYPE=type;
    }

    public double getFrequency() {
        return frequency;
    }

    public ChannelTypeEnum getTYPE() {
        return TYPE;
    }

    @Override
    public String toString(){
        return "주파수="+this.frequency+", 타입="+this.TYPE;
    }

}

Channel 클래스는 주파수와 언어를 정의해 놓은 도메인 클래스 입니다.

ChannelCollection.java

public interface ChannelCollection {
     void addChannel(Channel channel);
     void removeChannel(Channel channel);
     ChannelIterator iterator(ChannelTypeEnum type);
}

ChannelIterator.java

public interface ChannelIterator {
     boolean hasNext();
     Channel next();
}

이제 기본 인터페이스와 코어 클래스가 준비가 됐습니다. Collection 인터페이스를 구현해서 사용할 iterator 클래스를 구현해보겠습니다.

ChannelCollectionImpl.java

public class ChannelCollectionImpl implements ChannelCollection {

    private List<Channel> channelsList;

    public ChannelCollectionImpl() {
        channelsList = new ArrayList<>();
    }

    public void addChannel(Channel channel) {
        this.channelsList.add(channel);
    }

    public void removeChannel(Channel channel) {
        this.channelsList.remove(channel);
    }

    @Override
    public ChannelIterator iterator(ChannelTypeEnum type) {
        return new ChannelIteratorImpl(type, this.channelsList);
    }

    private class ChannelIteratorImpl implements ChannelIterator {

        private ChannelTypeEnum type;
        private List<Channel> channels;
        private int position;

        public ChannelIteratorImpl(ChannelTypeEnum ty,
                                   List<Channel> channelsList) {
            this.type = ty;
            this.channels = channelsList;
        }

        @Override
        public boolean hasNext() {
            while (position < channels.size()) {
                Channel channel = channels.get(position);
                if (channel.getTYPE().equals(type) || type.equals(ChannelTypeEnum.ALL)) {
                    return true;
                } else
                    position++;
            }
            return false;
        }

        @Override
        public Channel next() {
            Channel channel = channels.get(position);
            position++;
            return channel;
        }

    }
}

iterator 인터페이스의 구현을 inner 클래스에 구현했기 때문에 다른 collection에서는 이 클래스를 사용할 수 없습니다.

IteratorPatternTest.java

package com.donghyeon.designpattern.iterator;

public class IteratorPatternTest {

    public static void main(String[] args) {
        ChannelCollection channels = populateChannels();
        ChannelIterator baseIterator = channels.iterator(ChannelTypeEnum.ALL);
        System.out.println("모든 채널 찾기");
        while (baseIterator.hasNext()) {
            Channel channel = baseIterator.next();
            System.out.println(channel.toString());
        }
        System.out.println("******");
        // 영어로 된 채널 찾기
        System.out.println("영어로 된 채널 찾기");
        ChannelIterator englishIterator = channels.iterator(ChannelTypeEnum.ENGLISH);
        while (englishIterator.hasNext()) {
            Channel channel = englishIterator.next();
            System.out.println(channel.toString());
        }
    }

    private static ChannelCollection populateChannels() {
        ChannelCollection channels = new ChannelCollectionImpl();
        channels.addChannel(new Channel(98.5, ChannelTypeEnum.ENGLISH));
        channels.addChannel(new Channel(99.5, ChannelTypeEnum.HINDI));
        channels.addChannel(new Channel(100.5, ChannelTypeEnum.KOREAN));
        channels.addChannel(new Channel(101.5, ChannelTypeEnum.ENGLISH));
        channels.addChannel(new Channel(102.5, ChannelTypeEnum.HINDI));
        channels.addChannel(new Channel(103.5, ChannelTypeEnum.FRENCH));
        channels.addChannel(new Channel(104.5, ChannelTypeEnum.ENGLISH));
        channels.addChannel(new Channel(105.5, ChannelTypeEnum.KOREAN));
        channels.addChannel(new Channel(106.5, ChannelTypeEnum.FRENCH));
        return channels;
    }

}

Output

모든 채널 찾기
주파수=98.5, 타입=ENGLISH
주파수=99.5, 타입=HINDI
주파수=100.5, 타입=KOREAN
주파수=101.5, 타입=ENGLISH
주파수=102.5, 타입=HINDI
주파수=103.5, 타입=FRENCH
주파수=104.5, 타입=ENGLISH
주파수=105.5, 타입=KOREAN
주파수=106.5, 타입=FRENCH
******
영어로  채널 찾기
주파수=98.5, 타입=ENGLISH
주파수=101.5, 타입=ENGLISH
주파수=104.5, 타입=ENGLISH

Iterator 패턴 정리

  • Iterator 패턴은 collection의 순회를 표준 방법으로 제공하고, 클라이언트로부터 구현 로직을 숨기고자 할 때 유용합니다.
  • 순회 로직은 collection 자체에 내장되어 있어 클라이언트에서 쉽게 순회할수 있도록 해줍니다.

JDK에서 Iterator 패턴을 사용한 부분

Collection 프레임워크가 iterator 패턴을 잘 구현한 가장 좋은 예지만, java.util.Scanner 클래스또한 Iterator를 구현했습니다.