Iterator
Iterator – Problém 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ý seznam binární vyhledávací strom Mějme libovolnou kolekci čísel a chceme je všechna vypsat na obrazovku Hlavní podstata řešení úlohy se nemění, ale výrazně se liší okolní kontext, který závisí na zvolené reprezentaci samotné kolekce. Nešlo by to lépe?
Iterator – Použití a výhody Co je tedy hlavním přínosem iterátoru? dává nám snadný způsob, jak sekvenčně přistupovat k prvkům kolekce, a to bez nutnosti odhalovat její interní reprezentaci uživatel iterátoru se tak nemusí zatěžovat složitým principem fungování kolekce a může se zaměřit jen a pouze na úkol, který s její pomocí řeší A jaké jsou další výhody? zjednodušení rozhraní kolekce odlišné způsoby iterace nad kolekcí více současných iterací nad jednou kolekcí jednotné rozhraní pro iterování nad kolekcemi záměna kolekcí beze změny uživatelského kódu
Iterator – Základní forma Základní princip jednoznačné spárování iterátoru s danou kolekcí zodpovědnost za přístup k prvkům kolekce přenesena na iterátor k tomuto účelu definuje iterátor vhodné rozhraní iterátor samotný udržuje informaci o právě vybraném prvku a 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é není součástí návrhového vzoru prostor pro různé užitečné operace
Iterator – Základní forma - použití Příklad použití rozhraní iterátoru výpis jmen zaměstnanců z kolekce vybraných iterátorem 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(); } Jeden uživatelský kód, ale různé varianty všichni jen ti, kteří splňují určitou podmínku v obráceném sledu
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;
Iterator – Příklad implementace 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 ) { } };
Iterator – Užití vzoru Proxy Důsledkem užití vzoru Factory Method jsou dynamicky alokované iterátory je tedy otázkou, kdo by je měl dealokovat zřejmě uživatel, ten je ale nezodpovědný a hrozí tak úniky paměti Třída pracující ve smyslu návrhového vzoru Proxy tento problém řeší „obaluje“ iterátor a sama se později postará o jeho destrukci její instance jsou vytvářeny na zásobníku template class IteratorPtr { public: IteratorPtr( Iterator * it ) : _it( it ) { } ~IteratorPtr() { delete _it; } Iterator * operator->() { return _it; } Iterator & operator*() { return *_it; } private: Iterator * _it; IteratorPtr( const IteratorPtr & ); IteratorPtr & operator=( const IteratorPtr & ); }; AbstractList * employees; //... Iterator * it = employees->CreateIterator(); PrintEmployees( *it ); delete it; AbstractList * employees; //... IteratorPtr it( employees->CreateIterator()); PrintEmployees( *it ); // Na konci bloku je automaticky // zavolán destruktor IteratorPtr, // a dojde k dealokaci iterátoru.
Iterator – Rozdělení Podle toho, kdo řídí průběh iterace: externí iterátor 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 interní iterátor naopak řízení iterace obstarává přímo iterátor a uživatel pouze specifikuje, co má iterátor s prvky vykonat užitečné především v jazycích s podporou lambda funkcí pro některé typy operací se však vyloženě nehodí Podle toho, kdo implementuje algoritmus iterace nad kolekcí: iterátor určitě 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 v takovém případě mluvíme o tzv. variantě Cursor
Iterator – Otázky Jaké je chování při modifikacích kolekce během iterování? typicky nebezpečná a nepodporovaná operace lze řešit iterací nad kopií kolekce nebo robustnějším iterátorem Jak řešit okrajové podmínky bez speciálních ošetření? pomůže „degenerovaná“ varianta – tzv. Null iterátor z definice je vždy na konci kolekce a neukazuje na žádný prvek metoda IsDone() tedy vždy vrací hodnotu true Nad čím vším můžeme iterovat? cokoliv nás napadne, fantazii se meze nekladou lze např. realizovat „nekonečný“ iterátor pro proudová data načítána z externího zdroje nebo pro číselné posloupnosti (Fibonacciho aj.) iterování napříč XML dokumentem nebo nad adresářovou strukturou ……
Iterator – Zapouzdření Iterátor je velmi úzce spojen se svou kolekcí pokud je algoritmus iterace nad kolekcí umístěn v iterátoru (externí iterátor), tak typicky potřebuje přistupovat k vnitřní struktuře kolekce, a to nad rámec jejího veřejného rozhraní dochází tak „narušení“ zapouzdření třídy kolekce v C++ lze k vyjádření tohoto vztahu použít konstrukci friend v Javě lze stejného efektu docílit modifikátorem protected v rámci stejného package Přidání nového typu iterátoru však může být obtížné díky potřebě přidat nový iterátor jako friend třídu kolekce řešením je friend nadtřída všech iterátorů, která bude poskytovat protected metody, jenž budou „rozšiřovat“ 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 ) { //... } //... };
Iterator – Shrnutí na závěr Klíčové momenty polymorfní iterátor instance vytvářeny pomocí Factory Method a spravovány pomocí Proxy kolekce má jako friend třídu abstraktního předka iterátorů externí vs. interní iterátory – podle volby umístění řízení iterace normální vs. Cursor – podle volby umístění algoritmu 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 mimo jiné i na pozadí různých „foreach“ konstrukcí Jaké jsou související návrhové vzory? Factory Method Proxy Composite – iterace nad rekurzivní strukturou Memento – zachytávání stavu iterace