Wzorzec projektowy Iterator - okładka

Wzorzec projektowy Iterator

Opublikowano Kategorie Czysty kodCzas czytania 5min

Wzorzec projektowy Iterator to jeden z behawioralnych wzorców projektowych opisanych przez Gang of Four. W tym artykule poznasz jego specyfikę oraz dowiesz się, kiedy warto go wykorzystać. Przykłady kodu w artykule przygotowane zostały w TypeScripcie.

Ten wpis jest kolejnym wpisem z serii o wzorcach projektowych. Jeśli chcesz poznać inne wzorce projektowe lub dowiedzieć się czym są wzorce projektowe, to koniecznie sprawdź mój wpis o wzorcach projektowych.

Wzorzec projektowy Iterator w teorii

Gang of Four wskazał dla tego wzorca alternatywną nazwę — Kursor. Zarówno nazwa Iterator, jak i Kursor dość dobrze oddają istotę i cel tego wzorca. Pierwszym z celów Iteratora jest dostarczenie interfejsu pozwalającego użytkownikowi na przeiterowanie po zbiorze danych bez znajomości struktury tego zbioru oraz sposobów dostępu do danych w nim zawartych. Drugi z celów to kontrola procesu iteracji. Iterator jest odpowiedzialny za zapamiętanie, w którym miejscu aktualnie znajduje się użytkownik iterujący po zbiorze.

Stworzenie dedykowanego interfejsu do iterowania po zbiorach danych daje kilka korzyści:

  • Ukrywa szczegóły iteracji po konkretnych strukturach danych. Klient nie musi wiedzieć jak po nich iterować. Otrzymuje gotowy interfejs z opisem oczekiwanego zachowania;
  • Odpowiedzialność za iterowanie spoczywa na Iteratorze, co pomaga w zachowaniu SRP;
  • Ogranicza liczbę miejsc do zmiany w przypadku zmiany struktury danych. Podmiana np. listy na mapę nie wymaga zmian po stronie klienta. Ta cecha Iteratora jest określona polimorficzną iteracją;
  • Pozwala na rozbudowanie interfejsu o kolejne sposoby iteracji. Może to być np. zmiana kolejności iterowanych elementów, iteracja z filtrowaniem itd.
  • Można wielokrotnie, niezależnie iterować po danym zbiorze i zapamiętywać szczegóły związane z konkretnym procesem iteracji w konkretnym iteratorze.

Czy to oznacza, że w każdym miejscu, gdzie pojawia się lista, tablica czy drzewo trzeba wykorzystać Iteratora? Zdecydowanie nie. Pisałem to już wielokrotnie podczas omawiania innych wzorców. Wzorce należy wykorzystywać wtedy, gdy faktycznie jest to uzasadnione. Przykładowo, implementacja Iteratora w istotnym biznesowo kodzie może mieć sens. Separujemy wtedy kod istotny biznesowo od niskopoziomowych szczegółów technicznych. Jednak często zwykła pętla będzie wystarczająca, a użycie Iteratora będzie „armatą na muchę”.

Struktura wzorca Iterator

Wzorzec Iterator - diagram klas

Podstawą Iteratora są dwa interfejsy definiujące bazowe komponenty:

  • Iterator — definiuje metody służące iteracji. To jakie to są metody, zależy już od konkretnego przypadku. W podanym przykładzie wybrałem next()hasNext() inspirując się API Iteratora dostępnego w Javie.
  • Aggregate — definiuje kolekcję, po której będzie przemieszczał się iterator. Oczekiwane jest dostarczenie Factory Method służącej wyprodukowaniu Iteratora.

Na podstawie tych dwóch interfejsów, można zacząć tworzyć implementujące je klasy. W podanym przykładzie są to ConcreteAggregateConcreteIterator. To z tymi klasami w interakcję będzie wchodził klient.

Wzorzec projektowy Iterator w praktyce

Iterator potencjalnie znajdzie zastosowanie w każdym miejscu, gdzie wykonuje się iterowanie po zbiorze danych. Mogą to być na przykład:

  • Operacje na DOM — przemieszczanie się po drzewie i wykonywanie operacji na jego poszczególnych elementach;
  • Operacje na plikach w systemie — iteracja po plikach w danym katalogu i jego podkatalogach;
  • Implementacja klienta do API z paginacją zasobów;
  • Inne przypadki, gdzie występują operacje na strukturach danych — mapy, listy, stosy, drzewa itd.

W dalszej części artykułu znajdziesz przykład, gdzie zaimplementowałem Iterator do przechodzenia po zestawie plików w formie prostej tablicy.


interface IIterator<T> {
  next(): T | null;
  hasNext(): boolean;
}

interface ICollection {
  getIterator(): IIterator<string>;
}

class FileIterator implements IIterator<string> {
  private _index = 0;

  public constructor(
    private readonly _files: string[]
  ) {}

  public next(): string | null {
    if ( this.hasNext() ) {
      return this._files[ this._index++ ];
    }
    return null;
  }

  public hasNext(): boolean {
    return this._index < this._files.length;
  }
}

class ListFileCollection implements ICollection {
  public constructor(
    private readonly _files: string[]
  ) {}

  public getIterator(): IIterator<string> {
    return new FileIterator( this._files );
  }
}

const fileList = new ListFileCollection( [
  '/foo/bar/baz.jpg',
  '/2020/01/24/faktura.pdf',
  'assets/style.css',
  'secret.txt',
] );

function processFiles( fileCollection: ICollection ) {
  const iterator = fileCollection.getIterator();

  while ( iterator.hasNext() ) {
    console.log( 'Processing file: ', iterator.next() );
  }
}

processFiles( fileList );

Metoda processFile() jest odpowiednikiem wywołania Iteratora kodu biznesowego. Dodatkowo takie podejście pomaga spełnić OCP. Chcąc zamienić sposób przechowywania plików z listy np. na mapę, wystarczy zaimplementować kolejną klasę implementująca interfejs ICollection np. MapFileCollecton.

Podsumowanie

Iterator to wzorzec, który dość niewielkim nakładem pracy może polepszyć Twoje doświadczenia z iterowaniem 😉

Jeśli masz jakiś ciekawy praktyczny przykład, gdzie udało Ci się wykorzystać Iteratora, to zachęcam do podzielenia się nim w komentarzu.

Książkę Design Patterns: Elements of Reusable Object-Oriented Software możesz kupić, klikając TEN link. Kupując z tego linku, wspierasz rozwój bloga.

Źródła i materiały dodatkowe

Dominik Szczepaniak

Zawodowo Senior Software Engineer w CKSource. Prywatnie bloger, fan włoskiej kuchni, miłośnik jazdy na rowerze i treningu siłowego.

Inne wpisy, które mogą Cię zainteresować

Przygotuj się lepiej do rozmowy o pracę!

Odbierz darmowy egzemplarz e-booka 106 Pytań Rekrutacyjnych Junior JavaScript Developer i realnie zwiększ swoje szanse na rozmowie rekrutacyjnej! Będziesz też otrzymywać wartościowe treści i powiadomienia o nowych wpisach na skrzynkę e-mail.

Dlaczego warto?

  • 👉 Ponad 1000 pobrań e-booka!
  • 👉 60 stron pełnych pytań i zadań praktycznych. Pytania i zadania pochodzą z faktycznych procesów rekrutacyjnych.

E-booka odbierzesz korzystając z formularza poniżej 👇

Okładka e-booka - Kolejna książka o Gicie

Subscribe
Powiadom o
guest

0 komentarzy
oceniany
najnowszy najstarszy
Inline Feedbacks
View all comments