Что это такое
Паттерн Iterator обходит коллекцию, не раскрывая, как она хранится. Вызывающий код спрашивает «есть ли ещё элементы?» и «дай следующий» — а где живут данные: в срезе, в дереве или в базе данных — остаётся скрытым.
Для обычных срезов и карт итератором уже служит range из Go. Паттерн становится полезен, когда хранилище скрыто или обход устроен сложно.
Пример: обход плейлиста
Плейлист хранит свои песни приватно. Итератор предоставляет только HasNext() и Next() — хранилище за ними может полностью измениться, и ни один вызывающий код не сломается.
HasNext и Next
Цикл никогда не обращается к срезу внутри плейлиста. Замените срез на связный список или на ленивую загрузку — этот цикл всё так же будет компилироваться и работать.
Вывод: программа на Go и захваченный вывод
package main
import "fmt"
type Playlist struct {
songs []string
}
// Итератор обходит коллекцию, не раскрывая срез.
type Iterator struct {
playlist *Playlist
position int
}
func (p *Playlist) Iterator() *Iterator {
return &Iterator{playlist: p}
}
func (it *Iterator) HasNext() bool {
return it.position < len(it.playlist.songs)
}
func (it *Iterator) Next() string {
song := it.playlist.songs[it.position]
it.position++
return song
}
func main() {
p := &Playlist{songs: []string{"вступление", "куплет", "припев"}}
for it := p.Iterator(); it.HasNext(); {
fmt.Println("играет:", it.Next())
}
}
Когда его использовать
Используйте его, когда внутреннее устройство коллекции должно оставаться скрытым, когда обход нетривиален (деревья, страницы из API) или когда нужно несколько независимых обходов одновременно.
Откажитесь от него для срезов и карт в вашем собственном коде — range короче, быстрее и понятен каждому читателю.
Плюсы и минусы
Плюсы: хранилище остаётся скрытым и допускает изменения; несколько обходов могут выполняться независимо; сложный обход пишется один раз.
Минусы: кода больше, чем с range; активный итератор по изменяющейся коллекции — классический источник неочевидных ошибок.