Статьи

Шаблон проектирования итераторов в Java — пример учебника

Шаблон итератора в одном из поведенческих шаблонов, и он используется для обеспечения стандартного способа обхода группы объектов. Шаблон итератора широко используется в Java Collection Framework, где интерфейс Iterator предоставляет методы для обхода коллекции. Согласно GoF, намерение шаблона проектирования итератора:

Предоставляет способ доступа к элементам агрегатного объекта без раскрытия его базового представления.

Шаблон итератора — это не только обход коллекции, мы можем предоставить различные типы итераторов в зависимости от наших требований. Шаблон итератора скрывает фактическую реализацию обхода через коллекцию, а клиентские программы просто используют методы итератора.

Давайте разберемся с этой моделью на простом примере. Предположим, у нас есть список радиоканалов, и клиентская программа хочет просматривать их один за другим или в зависимости от типа канала, например, некоторые клиентские программы интересуются только английскими каналами и хотят обрабатывать только их, но не хочу обрабатывать другие типы каналов.

Таким образом, мы можем предоставить клиенту набор каналов и позволить им написать логику для обхода каналов и решить, обрабатывать ли их. Но это решение имеет много проблем, таких как клиент должен придумать логику обхода. Мы не можем убедиться в правильности клиентской логики, и если число клиентов будет расти, поддерживать его будет очень сложно.

Здесь мы можем использовать шаблон Iterator и предоставлять итерацию в зависимости от типа канала. Мы должны убедиться, что клиентская программа может получить доступ к списку каналов только через итератор.

Первая часть реализации заключается в определении контракта для нашего интерфейса коллекции и итератора.

ChannelTypeEnum.java

1
2
3
4
5
6
package com.journaldev.design.iterator;
 
public enum ChannelTypeEnum {
 
    ENGLISH, HINDI, FRENCH, ALL;
}

ChannelTypeEnum — это перечисление java, которое определяет все различные типы каналов.

Channel.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.journaldev.design.iterator;
 
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 "Frequency="+this.frequency+", Type="+this.TYPE;
    }
 
}

Channel — это простой класс POJO, который имеет атрибуты частоты и типа канала.

ChannelCollection.java

01
02
03
04
05
06
07
08
09
10
11
package com.journaldev.design.iterator;
 
public interface ChannelCollection {
 
    public void addChannel(Channel c);
 
    public void removeChannel(Channel c);
 
    public ChannelIterator iterator(ChannelTypeEnum type);
 
}

Интерфейс ChannelCollection определяет контракт для реализации нашего класса коллекции. Обратите внимание, что есть методы для добавления и удаления канала, но нет метода, который возвращает список каналов, и у него есть метод, который возвращает итератор для обхода. Интерфейс ChannelIterator определяет следующие методы;

ChannelIterator.java

1
2
3
4
5
6
7
8
package com.journaldev.design.iterator;
 
public interface ChannelIterator {
 
    public boolean hasNext();
 
    public Channel next();
}

Теперь наш базовый интерфейс и базовые классы готовы, давайте приступим к реализации класса коллекции и итератора.

ChannelCollectionImpl.java

01
02
03
04
05
06
07
08
09
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
48
49
50
51
52
53
54
55
56
57
58
59
package com.journaldev.design.iterator;
 
import java.util.ArrayList;
import java.util.List;
 
public class ChannelCollectionImpl implements ChannelCollection {
 
    private List<Channel> channelsList;
 
    public ChannelCollectionImpl() {
        channelsList = new ArrayList<>();
    }
 
    public void addChannel(Channel c) {
        this.channelsList.add(c);
    }
 
    public void removeChannel(Channel c) {
        this.channelsList.remove(c);
    }
 
    @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 c = channels.get(position);
                if (c.getTYPE().equals(type) || type.equals(ChannelTypeEnum.ALL)) {
                    return true;
                } else
                    position++;
            }
            return false;
        }
 
        @Override
        public Channel next() {
            Channel c = channels.get(position);
            position++;
            return c;
        }
 
    }
}

Обратите внимание на реализацию внутреннего класса интерфейса итератора, так что реализация не может быть использована какой-либо другой коллекцией. Тот же подход следует за классами коллекции, и все они имеют реализацию внутреннего класса интерфейса Iterator.

Давайте напишем простой тестовый класс, чтобы использовать нашу коллекцию и итератор для обхода коллекции каналов по типу.

IteratorPatternTest.java

01
02
03
04
05
06
07
08
09
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
package com.journaldev.design.iterator;
 
public class IteratorPatternTest {
 
    public static void main(String[] args) {
        ChannelCollection channels = populateChannels();
        ChannelIterator baseIterator = channels.iterator(ChannelTypeEnum.ALL);
        while (baseIterator.hasNext()) {
            Channel c = baseIterator.next();
            System.out.println(c.toString());
        }
        System.out.println("******");
        // Channel Type Iterator
        ChannelIterator englishIterator = channels.iterator(ChannelTypeEnum.ENGLISH);
        while (englishIterator.hasNext()) {
            Channel c = englishIterator.next();
            System.out.println(c.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.FRENCH));
        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.HINDI));
        channels.addChannel(new Channel(106.5, ChannelTypeEnum.FRENCH));
        return channels;
    }
 
}

Когда я запускаю вышеуказанную программу, она выдает следующий вывод;

IteratorPatternTest.java

01
02
03
04
05
06
07
08
09
10
11
12
13
Frequency=98.5, Type=ENGLISH
Frequency=99.5, Type=HINDI
Frequency=100.5, Type=FRENCH
Frequency=101.5, Type=ENGLISH
Frequency=102.5, Type=HINDI
Frequency=103.5, Type=FRENCH
Frequency=104.5, Type=ENGLISH
Frequency=105.5, Type=HINDI
Frequency=106.5, Type=FRENCH
******
Frequency=98.5, Type=ENGLISH
Frequency=101.5, Type=ENGLISH
Frequency=104.5, Type=ENGLISH

Важные точки

  • Шаблон итератора полезен, когда вы хотите предоставить стандартный способ перебора коллекции и скрыть логику реализации от клиентской программы.
  • Логика итерации встроена в саму коллекцию и помогает клиентской программе легко выполнять итерации по ним.

Шаблон итератора в JDK

Мы все знаем, что Framework Framework Iterator является лучшим примером реализации шаблона итератора, но знаете ли вы, что класс java.util.Scanner также реализует интерфейс Iterator. Прочтите этот пост, чтобы узнать о классе Java Scanner.

Это все для шаблона итератора, я надеюсь, что он полезен и прост для понимания.