Iterator
C historie int * rand_numbers(int n) { int *numbers = malloc(n * sizeof(int)); int *it = numbers; while (it < numbers + n) *it++ = rand(); // vlastně jsem chtěl vygenerovat 2n čísel numbers = realloc(numbers, 2 * n * sizeof(int)); // it = numbers + n; while (it < numbers + (2 * n)) *it++ = rand(); return numbers; } Nejjednodušší formou iterátoru je ukazatel inkrementace ukazatele znamená posun iterátoru Objekt nebo jeho kritická část nesmí ukazateli za zády zmizet "vetchý" iterátor programátoři implicitně chápou takové chování rys vlastní většině iterátorů dodnes
Iterator – Motivace struct Item { int _value; Item * _next; Item( int value, Item * next ) : _value( value ), _next( next ) { } }; void Print( Item * list ) { for( Item * item = list; item != 0; item = item->_next){ std::cout _value << std::endl; } struct Node { int _value; Node * _left; Node * _right; Node( int value, Node * left, Node * right ) : _value( value ), _left( left ), _right( right ) { } }; void Print( Node * tree ) { if (tree != 0) { Node * node = tree; Print( node->_left ); std::cout _value << std::endl; Print( node->_right ); } lineární spojový seznambinární vyhledávací strom Mějme kolekci čísel kterou chceme celou vypsat na obrazovku Implementace libovolné funkce závisí na konkrétní kolekci Co když chceme napsat knihovnu použitelnou s libovolnou kolekcí?
Iterator – Cíl, použití a výhody Knihovna kolekcí uživatel kolekce se nemusí zatěžovat složitým principem fungování kolekce může se zaměřit jen na úkol, který řeší pokud mu stačí sekvenční přístup Další výhody zjednodušení rozhraní kolekce odlišné způsoby iterace nad kolekcí více současných iterací nad jednou kolekcí záměna kolekcí beze změny uživatelského kódu Proč iterátor? chceme snadné a jednotné rozhraní pro sekvenční přistup k prvkům kolekce bez nutnosti odhalovat její interní reprezentaci Knihovna algoritmů funkce definované v knihovně nemusí znát konkrétní kolekci, nad kterou pracují uživatel si může definovat vlastní kolekce do jednoho frame - na všech slajdech tam kde je to vhodné!
Iterator – Základní forma Základní principy jednoznačné spárování iterátoru s kolekcí zodpovědnost za přístup k prvkům kolekce přenesena na iterátor iterátor udržuje informaci o aktuálním prvku je schopen určit prvek následující Rozhraní iterátoru minimální inicializace na první prvek posun na následující prvek test na konec získání aktuálního prvku další možná rozšíření posun na předchozí prvek posun na libovolný prvek … Rozhraní kolekce zůstává jednoduché
Iterator – Základní forma - použití Příklad použití rozhraní iterátoru (GoF, C++) výpis jmen zaměstnanců z kolekce struct Employee { std::string _name; void Print() { std::cout << _name << std::endl; } }; void PrintEmployees( ListIterator & it ) { for ( it.First(); !it.IsDone(); it.Next() ) { it.CurrentItem()->Print(); } Funkce PrintEmployees() je stále svázána s kolekcí List konkrétní iterátor je vždy svázán s danou kolekcí nutno využít polymorfismus (rozhraní)
Iterator – Polymorfismus obecná rozhraní konkrétní implementace iterátoru konkrétní implementace kolekce
Iterator – Polymorfismus Abstraktní kolekce (Aggregate) rozhraní pro vytváření iterátorů Konkrétní kolekce vytváří instance konkrétního iterátoru Abstraktní iterátor (Iterator) rozhraní pro sekvenční iteraci nad kolekcí Konkrétní iterátor implementace rozhraní iterátoru udržuje informaci o právě vybraném prvku Factory Method
Iterator – Užití vzoru Factory Method Řešení problému, jak vytvářet instance konkrétních iterátorů abychom mohli psát kód, který je nezávislý na použité konkrétní kolekci Důsledky jde o „propojení“ obou hierarchií tříd přináší dynamickou alokaci instancí iterátorů AbstractList employees; //... Iterator * it = employees.CreateIterator(); PrintEmployees( *it ); delete it; unique_ptr > it( employees.CreateIterator()); PrintEmployees( *it ); Iterator it = employees.CreateIterator(); PrintEmployees( it ); C++98 C++11 JAVA
Iterator – Příklad implementace (GoF, C++98) template class Iterator { public: virtual void First() = 0; virtual void Next() = 0; virtual bool IsDone() const = 0; virtual Item CurrentItem() const = 0; protected: Iterator(); }; template class AbstractList { public: virtual Iterator * CreateIterator() const = 0; }; template class List : public AbstractList { public: //... long Count() const; Item & Get( long index ) const; //... virtual Iterator * CreateIterator() const { return new ListIterator ( this ); } }; template class ListIterator : public Iterator { protected: const List * _list; long _current; public: virtual void First() { _current = 0; } virtual void Next() { _current++; } virtual bool IsDone() const { return _current >= _list->Count(); } virtual Item CurrentItem() const { if ( !IsDone() ) { return _list->Get( _current ); } else { throw IteratorOutOfBounds; } ListIterator( const List * list ) : _list( list ), _current( 0 ) { } }; unique_ptr<>
Iterator – Rozdělení Podle toho, kdo řídí průběh iterace: externí iterátor (procedurální pohled na iteraci) uživatel se sám explicitně dotazuje iterátoru na následující prvek kolekce řízení iterace tak leží výhradně na uživateli flexibilnější, ale „složitější“ varianta snadno pochopitelné, přímá analogie s ukazateli interní iterátor (funkcionální pohled na iteraci) řízení iterace obstarává přímo iterátor uživatel pouze specifikuje akce s prvky užitečné především v jazycích s podporou lambda funkcí pro některé typy operací se nehodí (např. porovnání dvou kolekcí) Podle toho, kdo implementuje algoritmus iterace nad kolekcí: iterátor flexibilnější jde naproti znovupoužitelnosti kódu typicky ale „narušuje“ zapouzdření nebo samotná kolekce iterátor pouze udržuje aktuální pozici v kolekci tzv. Cursor (pozor: nejednotná terminologie)
Iterator – Otázky Jaké je chování při modifikacích kolekce během iterování? typicky nebezpečná operace bez možnosti kontroly v čase kompilace lze řešit iterací nad kopií kolekce nebo robustnějším iterátorem např. kolekce vede evidenci iterátorů a tyto dle potřeby aktualizuje Robert B. Murray. C++ Strategies and Tactics. Addison-Wesley, Reading, MA, nevýhoda mnoha implementací iterátorů Nad čím vším můžeme iterovat? cokoliv nás napadne, fantazii se meze nekladou virtuální nebo „nekonečný“ iterátor proudová data načítána z externího zdroje číselné posloupnosti (Fibonacciho aj.), generátor kolekce je generována iterátorem, prvky nejsou přítomny v paměti filtr - výběr pouze určitých prvků dle predikátu XML dokument, adresářová struktura, DB resultset,...,...
Iterator – Zapouzdření Iterátor je velmi úzce spojen se svou kolekcí externí iterátor - potřebuje přistupovat k vnitřní struktuře kolekce nad rámec veřejného rozhraní „narušení“ zapouzdření třídy kolekce C++: konstrukce friend Java: modifikátor protected v rámci stejné package Přidání nového typu iterátoru může být obtížné potřeba přidat nový iterátor jako friend třídu kolekce řešení: friend nadtřída všech iterátorů poskytuje protected metody, rozšiřující rozhraní kolekce pro potřeby iterátorů
Iterator – Zapouzdření class ListIterator : public Iterator { private: List * _list; protected: bool _LstIsOut( Item * item ) { return _list->_IsOut( item ); } Item * _LstGetNext( Item * item ) { return _list->_GetNext( item ); } //... }; class ForwardListIterator : public ListIterator { private: Item * _current; public: bool IsDone() { return _LstIsOut( _current ); } void Next() { _current = _LstGetNext( _current ); } Item CurrentItem() { return *_current; } //... }; class List : public AbstractList { friend class ListIterator; private: bool _IsOut( Item * item ) { //... } Item * _GetNext( Item * item ) { //... } //... }; Item je interně použit jako Cursor
Iterator – Shrnutí Klíčové momenty iterator s jednotným rozhraním instance vytvářeny pomocí Factory Method externí vs. interní iterátory – podle volby umístění řízení iterace normální vs. Cursor – podle volby umístění algoritmu iterace externí iterátory nebývají odolné vůči změnám kolekce v průběhu iterace Kde se s iterátory setkáme kolekce v objektově orientovaných jazycích – C++, C#, Java, … Java poskytuje rozhraní Iterable – třída pouze implementuje na pozadí konstrukcí „foreach“ posun iterátorů z knihoven do syntaxe jazyků Související návrhové vzory Factory Method - vytváření iterátorů Proxy - ochrana syrových ukazatelů, resource management Composite - iterace nad rekurzivní strukturou Memento - zachytávání stavu iterace