Bu yazı Design Patterns/Tasarım Desenleri nedir? başlıklı yazı dizisinin bir parçasıdır.

Bu içerik ağırlıklı olarak refactoring.guru sitesindeki içeriğin tercümesi ve derlenmesinden oluşturulmuştur.

Tüm tasarım desenleri ya da diğer adıyla tasarım kalıplarına yönelik ayrıntılı içeriklere yazının sonundaki bağlantılardan ulaşabilirsiniz.

Iterator Tasarım Deseninin Amacı

Iterator, bir koleksiyonun öğelerini tiplerinden bağımsız (list, stack, tree vb.) tarayarak tekrarlı bir işlem yapmak istediğiniz durumlarda kullanılır.

Sorun

Koleksiyonlar programlamada en çok kullanılan veri tiplerinden biridir. Aslında koleksiyonlar bir grup nesne barından kapsayıcılardan başka bir şey de değildir.

Programlamada koleksiyonlar
Çeşitli koleksiyon tipleri

Çoğu koleksiyon elemanlarını basit bir listede tutar. Fakat bazıları yığın, ağaç, graph veya başka kompleks yapılarda olabilir.

Koleksiyonun yapısı ne olursa olsun, elemanlarının başka kodlar tarafından erişilir olması için yöntemler sağlamalıdır. Tekrar tekrar aynı elemanlarla karşılaşmadan bütün elemanların üzerinden geçmek için bir yöntem olmalıdır.

Eğer liste bazlı bir koleksiyonla uğraşıyorsanız bu basit bir işlem gibi görünebilir. Baştan başlayarak sona doğru bütün elemanlar üzerinden basitçe geçebilirsiniz. Peki ya ağaç gibi daha kompleks bir yapı ile çalışıyorsanız? Bir gün dikey bir dolaşım tercih ederken, ertesi gün yatay bir dolaşım yapma ihtiyacınız olabilir. Aradan bir hafta geçince de tüm öğeler üzerinden rasgele seçim yöntemi ile geçmek isteyebilirsiniz.

Aynı koleksiyonda farklı şekillerde dolaşılabilir

Koleksiyona daha da farklı dolaşım algoritmaları eklemek, zamanla birincil görevi olan verimli veri depolamayı arka planda bırakacaktır. Ek olarak bazı algoritmalar sadece belirli uygulamalara özel olabilir ve bunları genel bir koleksiyona eklemek tuhaf olacaktır.

Öte yandan istemci kodu çalıştığı çeşitli koleksiyonların elemanları nasıl sakladığı ile de ilgilenmek zorunda değildir. Koleksiyonlar elemanlara erişmek için farklı yollar sağladığından ana kodunuzu bu sınıflara bağımlı hale getirmiş olursunuz.

Çözüm

Iterator deseninin ana fikri bir koleksiyonun dolaşımla ilgili davranışlarını alıp iterator denen ayrı bir objeye yüklemektir.

Iterator deseni nedir
Iteratorler çeşitli dolaşım algoritmaları sağlarlar. Birden fazla iterator nesnesi aynı koleksiyon üzerinde aynı anda dolaşım yapabilir.

Iterator nesneyi algoritmayı sağlamanın yanı sıra mevcut pozisyon, kalan eleman sayısı vb. dolaşımla ilgili tüm detayları da saklar. Bu nedenle birden fazla iterator, aynı koleksiyon üzerinde ve aynı anda birbirinden bağımsız olarak dolaşım sağlayabilir.

Iteratörler genel olarak bir koleksiyondan elemanları almak için tek bir ana metot sağlarlar. İstemci artık hiç bir sonuç dönmeyene, yani eleman listesinin sonuna gelene kadar bu metodu çağırır.

Tüm iteratorler aynı arayüzden türemek zorundadır. Bu gerekli iteratör oldukça istemci kodun herhangi bir koleksiyon ya da algoritma ile dolaşım yapabilmesini sağlar. Bir koleksiyonda dolaşmak için özel bir yönteme ihtiyacınız varsa sadece gerekli iterator snıfını oluşturur ve istemci kodu ya da koleksiyonu değiştirmeden bu ihtiyacı karşılayabilirsiniz.

Uygulanabilirlik

Koleksiyonunuzun kompleks bir yapısı varsa fakat bunu istemci koda yansıtmak istemiyorsanız ( konfor ya da güvenlik nedeniyle ) iterator desenini kullanabilirsiniz.

Iterator, kompleks bir veri yapısı ile çalışmak için gereken detayları kapsamına alarak istemci koda koleksiyondaki elemanlara ulaşmaları için basit metotlar sağlar. Bu yaklaşım istemci için rahatlık sağlarken aynı zamanda koleksiyonu istenmeyen veya hatalı kullanımlara karşı da korumuş olur.

Uygulamanızdaki dolaşım kodlarının kopyala/yapıştır gereksinimi ortadan kaldırmak için bu deseni kullanabilirsiniz.

Koleksiyon içinde dolaşım kodları genelde şişkin olurlar ve uygulamanın işlev mantığı içine yerleştirildiğinde kodu birincil görevinden uzaklaştırıp yönetimini daha zor hale getirirler. Dolaşımla ilgili kodları iteratorler içerisine almak kodu daha yalın ve temiz hale gtirir.

Kodunuzun farklı veri yapılarında dolaşım yapması, veya veri yapısının belirsiz olduğu durumlarda iterator desenini kullanabilirsiniz.

Desen koleksiyonlar ve iteratorler için bazı genel arayüzler sağlar. Kodunuz bu arayüzleri kullanıyorsa, bu arayüzlerden türemiş koleksiyonlar ve iteratorler ile rahatlıkla çalışabilir.

Diğer tasarım desenleri/kalıpları ile ilişkisi

  • Composite yapıda ağaçlarda dolaşmak için Iteratorleri kullanabilirsiniz.
  • Koleksiyon alt sınıflarının, koleksiyonlarla uyumlu farklı iteratorler döndürebilmesi için Iterator ve Factory Method‘u birlikte kullanabilirsiniz.
  • Mevcut dolaşım durumunu yakalamak ve gerekirse geri döndürmek için Iterator ve Memento‘yu birlikte kullanabilirsiniz.
  • Kompleks bir veri yapısında dolaşmak, sınıfları farklı olsa bile elemanları üzerinde çeşitli işlemler gerçekleştirmek için Visitor ve Iteratorü birlikte kullanabilirsiniz.

Iterator Deseni Kod Örnekleri

Örnek PHP Kodu

<?php

namespace RefactoringGuru\Iterator\Conceptual;

/**
 * Concrete Iterators implement various traversal algorithms. These classes
 * store the current traversal position at all times.
 */
class AlphabeticalOrderIterator implements \Iterator
{
    /**
     * @var WordsCollection
     */
    private $collection;

    /**
     * @var int Stores the current traversal position. An iterator may have a
     * lot of other fields for storing iteration state, especially when it is
     * supposed to work with a particular kind of collection.
     */
    private $position = 0;

    /**
     * @var bool This variable indicates the traversal direction.
     */
    private $reverse = false;

    public function __construct($collection, $reverse = false)
    {
        $this->collection = $collection;
        $this->reverse = $reverse;
    }

    public function rewind()
    {
        $this->position = $this->reverse ?
            count($this->collection->getItems()) - 1 : 0;
    }

    public function current()
    {
        return $this->collection->getItems()[$this->position];
    }

    public function key()
    {
        return $this->position;
    }

    public function next()
    {
        $this->position = $this->position + ($this->reverse ? -1 : 1);
    }

    public function valid()
    {
        return isset($this->collection->getItems()[$this->position]);
    }
}

/**
 * Concrete Collections provide one or several methods for retrieving fresh
 * iterator instances, compatible with the collection class.
 */
class WordsCollection implements \IteratorAggregate
{
    private $items = [];

    public function getItems()
    {
        return $this->items;
    }

    public function addItem($item)
    {
        $this->items[] = $item;
    }

    public function getIterator(): Iterator
    {
        return new AlphabeticalOrderIterator($this);
    }

    public function getReverseIterator(): Iterator
    {
        return new AlphabeticalOrderIterator($this, true);
    }
}

/**
 * The client code may or may not know about the Concrete Iterator or Collection
 * classes, depending on the level of indirection you want to keep in your
 * program.
 */
$collection = new WordsCollection();
$collection->addItem("First");
$collection->addItem("Second");
$collection->addItem("Third");

echo "Straight traversal:\n";
foreach ($collection->getIterator() as $item) {
    echo $item . "\n";
}

echo "\n";
echo "Reverse traversal:\n";
foreach ($collection->getReverseIterator() as $item) {
    echo $item . "\n";
}

Örnek Python Kodu

from __future__ import annotations
from collections.abc import Iterable, Iterator
from typing import Any, List


"""
To create an iterator in Python, there are two abstract classes from the built-
in `collections` module - Iterable,Iterator. We need to implement the
`__iter__()` method in the iterated object (collection), and the `__next__ ()`
method in theiterator.
"""


class AlphabeticalOrderIterator(Iterator):
    """
    Concrete Iterators implement various traversal algorithms. These classes
    store the current traversal position at all times.
    """

    """
    `_position` attribute stores the current traversal position. An iterator may
    have a lot of other fields for storing iteration state, especially when it
    is supposed to work with a particular kind of collection.
    """
    _position: int = None

    """
    This attribute indicates the traversal direction.
    """
    _reverse: bool = False

    def __init__(self, collection: WordsCollection, reverse: bool = False) -> None:
        self._collection = collection
        self._reverse = reverse
        self._position = -1 if reverse else 0

    def __next__(self):
        """
        The __next__() method must return the next item in the sequence. On
        reaching the end, and in subsequent calls, it must raise StopIteration.
        """
        try:
            value = self._collection[self._position]
            self._position += -1 if self._reverse else 1
        except IndexError:
            raise StopIteration()

        return value


class WordsCollection(Iterable):
    """
    Concrete Collections provide one or several methods for retrieving fresh
    iterator instances, compatible with the collection class.
    """

    def __init__(self, collection: List[Any] = []) -> None:
        self._collection = collection

    def __iter__(self) -> AlphabeticalOrderIterator:
        """
        The __iter__() method returns the iterator object itself, by default we
        return the iterator in ascending order.
        """
        return AlphabeticalOrderIterator(self._collection)

    def get_reverse_iterator(self) -> AlphabeticalOrderIterator:
        return AlphabeticalOrderIterator(self._collection, True)

    def add_item(self, item: Any):
        self._collection.append(item)


if __name__ == "__main__":
    # The client code may or may not know about the Concrete Iterator or
    # Collection classes, depending on the level of indirection you want to keep
    # in your program.
    collection = WordsCollection()
    collection.add_item("First")
    collection.add_item("Second")
    collection.add_item("Third")

    print("Straight traversal:")
    print("\n".join(collection))
    print("")

    print("Reverse traversal:")
    print("\n".join(collection.get_reverse_iterator()), end="")

Diğer Tasarım Kalıpları/Design Patterns

Yaratımsal Kalıplar (Creational Patterns)

Yapısal Kalıplar (Structural Patterns)

Davranışsal Kalıplar (Behavioral Patterns)