1. Co to jest wzorzec Iterator
Wzorzec iterator to wzorzec projektowy behawioralny, który zapewnia jednolitą metodę przechodzenia elementów obiektu agregatu bez ujawniania wewnętrznej reprezentacji obiektu agregatu.
2. Charakterystyka i Zalety wzorca Iterator
Następujące są cechy i zalety wzorca iterator:
- Może ukryć wewnętrzną strukturę obiektu kolekcji, odłączając algorytm przechodzenia od obiektu kolekcji.
- Zapewnia ujednolicony sposób przechodzenia różnymi rodzajami obiektów kolekcji.
- Upraszcza kod klienta, sprawiając, że jest bardziej czytelny i zwięzły.
- Może dostarczać różne implementacje iteratorów, aby dostosować się do różnych potrzeb przechodzenia.
3. Przykłady praktycznych zastosowań wzorca Iterator
Wzorzec iteratora ma wiele praktycznych zastosowań, takich jak:
- Przechodzenie wyników zapytania bazy danych.
- Przechodzenie plików i folderów w systemie plików.
- Przechodzenie elementów w kolekcji.
4. Implementacja wzorca Iterator w języku Golang
4.1 Diagram klas UML
4.2 Wprowadzenie przykładu
Na powyższym diagramie klas UML mamy dwie główne role: Iterator i Collection.
- Iterator definiuje interfejs do przechodzenia obiektu kolekcji, w tym metodę
HasNext()
do określania, czy jest kolejny element, oraz metodęNext()
do pobrania następnego elementu. - ConcreteIterator to konkretne klasa implementacji Iteratora, implementująca metody
HasNext()
orazNext()
. - Collection definiuje interfejs do tworzenia obiektów iteratora za pomocą metody
CreateIterator()
. - ConcreteCollection to konkretne klasa implementacji Collection, implementująca metodę
CreateIterator()
.
4.3 Kroki implementacji
Następnie wykonamy krok po kroku implementację wzorca Iterator w języku Golang.
4.3.1 Zdefiniuj interfejs Iteratora oraz konkretną klasę Iteratora
type Iterator interface {
HasNext() bool
Next() interface{}
}
type ConcreteIterator struct {
collection *ConcreteCollection
index int
}
func (it *ConcreteIterator) HasNext() bool {
if it.index < len(it.collection.items) {
return true
}
return false
}
func (it *ConcreteIterator) Next() interface{} {
if it.HasNext() {
item := it.collection.items[it.index]
it.index++
return item
}
return nil
}
4.3.2 Zdefiniuj interfejs obiektu iterowalnego oraz konkretną klasę obiektu iterowalnego
type Collection interface {
CreateIterator() Iterator
}
type ConcreteCollection struct {
items []interface{}
}
func (c *ConcreteCollection) CreateIterator() Iterator {
return &ConcreteIterator{
collection: c,
index: 0,
}
}
4.3.3 Implementacja logiki generowania iteratora w klasie obiektu iterowalnego
func main() {
collection := &ConcreteCollection{
items: []interface{}{"Golang", "Python", "Java"},
}
iterator := collection.CreateIterator()
for iterator.HasNext() {
item := iterator.Next()
fmt.Println(item)
}
}
Uruchomienie powyższego kodu spowoduje wygenerowanie następującego wyniku:
Golang
Python
Java
W powyższym kodzie definiujemy klasę ConcreteCollection
, która implementuje interfejs Collection
, a jej metoda CreateIterator()
zwraca obiekt iteratora. Używamy tego obiektu iteratora w funkcji main()
do przechodzenia.
4.4 Wydłużenie kroku implementacji: Uproszczenie implementacji iteratora przy użyciu funkcji generatorowej
W Golang, możemy uprościć implementację iteratorów przy użyciu funkcji generatorowej (yield
). Oto przykład użycia funkcji generatorowej:
func GenerateItems() <-chan interface{} {
items := []interface{}{"Golang", "Python", "Java"}
out := make(chan interface{})
go func() {
defer close(out)
for _, item := range items {
out <- item
}
}()
return out
}
func main() {
for item := range GenerateItems() {
fmt.Println(item)
}
}
W powyższym kodzie definiujemy funkcję GenerateItems()
, która zwraca kanał tylko do odczytu (<-chan
), i używamy yield
do sekwencyjnego przesyłania elementów do kanału w ramach tej funkcji. W funkcji main()
używamy range
do przeglądania tego kanału tylko do odczytu i wyświetlamy wartości elementów.
W ten sposób udało nam się uprościć implementację iteratora przy użyciu funkcji generatorowej.