Шаблон итератора в одном из поведенческих шаблонов, и он используется для обеспечения стандартного способа обхода группы объектов. Шаблон итератора широко используется в 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.
Это все для шаблона итератора, я надеюсь, что он полезен и прост для понимания.