Поведенчески шаблони

Информатика и компютърни науки Тема

Илия Попов ИСТ-2 ФК 20251421006

Шаблонът „Итератор“(Iterator)
Предназначение:
Предоставя достъп до всички елементи на един съставен обект, без да разкрива неговото вътрешно
представяне.

Мотивация
Практически всяко приложение в една или друга степен използва колекции от данни. Когато
програмираме по някога ни се налага да използваме вектори, списъци, дървета или хеш таблици. В някои
случаи за обработката на данни на колекция се използва специфичен интерфейс на конкретни колекции,
но в определени случаи вътрешният достъп се осъществява, чрез специалния абстрактен слой
итераторът. Итераторите предоставят абстрактен интерфейс за достъп до съдържанието на съставни
обекти, без да разкриват на клиента тяхната вътрешна структура. В резултат се получава ясно разделение
на отговорностите:
 Клиентите получават възможност да работят с различни колекции по унифициран начин, а
класовете на колекциите стават по прости, благодарение на това, че отговорността за
обхождането на елементите им се възлага на отделна същност.
 Итераторите са до такава степен вкоренени в повечето езици и платформи, че тяхната поддръжка
се появява дори на ниво езици за програмиране като цикъла foreach в C#, range-for в C++ 11, for в
Java 5+ . Дори в консервативния Eiffel е реализирана аналогична конструкция across. Нещо
повече, много езици поддържат не само използване на итератори с помощта на циклите foreach и
разновидностите му, но и тяхното създаване, благодарение итератор блокове(Iterator Block в C#
и VB) или така наречените конструктори на последователности „SEQUENCE
COMPREHENSION “ достъпни в F#, Scala, Python и много други.

Итераторите са изключително подходящи за достъпване на данни от някакъв източник. Така например,
вместо класа LogFileReader бихме могли да използваме класа LogFileSource, който да имплементира
интерфейса IEnumerable<LogEntry> показан на листинг 1.












листинг 1. Екранна снимка на класа LogFileSource.

Илия Попов ИСТ-2 ФК 20251421006


Шаблонът „Итератор“ е от малкото шаблони, който реализиран в .NET Framework от книгата на „бандата
на четиримата“ практически в непроменен вид. Ако вземем изходната диаграма на класовете от книгата
Design Patterns, заменим Aggregate с IEnumerable<T>, iterator с IEnumerator<T> и променим малко
методите на класа Iterator, ще получим много близка картина както е показано на фиг. 1 и фиг.2.

фигура 1.Класическа диаграма на шаблона „Итератор“
Участници:

 Iterator (IEnumerator<T>) – определя интерфейса на итератора.
 Aggregate (IEnumerable<T>) – съставен обект, по който може да се премества итераторът.
 ConcreteAggregate1 (List<T>) – конкретна имплементация на агрегата.
 ConcreteIterator(List.Enumerator<T>) – конкретна имплементация на итератора за определен
агрегат.
Обсъждане

Итераторите в .NET са еднопосочни итератори само за четене. За получаването на итератор се използва
методът GetEnumerator на интерфейса IEnumerable, който всеки път връща нова инстанция на итератора.
Интерфейсът IEnumerator също е много опростен:
 MoveNext – преминаване към следващия елемент на агрегата. Връща false, ако е достигнат краят
на последователността.
 Current – връща текущия елемент.
 Reset – връща итератора в началото на агрегата. Не винаги се имплементира.



фигура 2. Реализация на шаблонът „Итератор“ в .NET Framework

Илия Попов ИСТ-2 ФК 20251421006

Веднага след създаването си итераторът сочи към първия елемент, затова за преминаване към първия
елемент трябва да се извика функцията MoveNext() минимум веднъж. Итераторите в .NET могат да
изглеждат доста примитивни, особено в сравнение с итераторите в C++. Стандартът на C++ определя
няколко различни типа итераторите input ←Forward ←Bidirectional ←RandomAccess. В C, C++ има дори
OutputIterator. Тоест итератор за извеждане на данни. Платформата .NET поддържа два типа итератори
негенерични и генерични. Първите се появяват в първата версия на платформата, а вторите са добавени
във втората версия заедно с генериците(generics) и итератор блоковете(iterator blocks)

Особености на итераторите в C# .NET

Контракт на итераторите. Всеки клас или интерфейс притежава контракт. Понякога контрактът е
явен и тогава той се дефинира с помощта на съответните инструменти от реда на Code Contracts. Друг
път той е неявен и тогава за разбиране на принципите за използване, предусловията и инвариантите е
необходимо да прочетем документацията в секция remarks дори когато трябва да вникнем в детайлите на
имплементацията. При инсталиране на библиотеката Code Contracts на разположение се появяват
контрактите на всички BCL типове. Вградените контракти на интерфейсите IEnumerator, IEnumerable
обаче не са много хубави затова нека да ги изведем самостоятелно.
Интересуват ни само контрактите на функцията MoveNext и свойството Current.

Контракт на свойството Current:
 Предусловие:
Итераторът не трябва да се освобождава посредством извикване на Dispose.
Итераторът трябва да реферира към конкретна стойност потребителският код трябва да извиква
функцията MoveNext, която трябва да върне стойност true.
 Постусловие:
Свойството Current трябва да върне стойността към която реферира итераторът. Това условие не
може да се изрази във формата на предикат, затова се използва Contract.Ensures(true, “”).
Итераторът не налага ограничение дали свойството може да върне null, или не.


Контракт на функцията MoveNext:
 Предусловие:
Итераторът не трябва да се освобождава посредством извикване на Dispose. Итераторът трябва
да бъде валиден, колекцията не трябва да бъде променяна след получаване на текущия итератор.
 Постусловие:
Ако итераторът не е достигнал до края на колекцията, ще премине на следващия елемент и
функцията ще върне true, в противен случай ще върне false. Тук има няколко важни момента.
Първо, предусловието на свойството Current е по слабо от предусловието на MoveNext. На
практика свойството Current въобще няма предусловие можем да се обърнем към свойството
след извикване на Dispose и преди извикването на MoveNext и няма да се получи изключение.
Ще приложим тези изисквания към контракта, тъй като никой съзнателно не трябва да се
обръща към свойството Current без изпълнението на тези условия. И ще добавя още един
момент, свързан с MoveNext никой не е забранил да го извикваме при завършен итератор. В
този случай MoveNext ще върне false. Това е по валидно изискване, тъй като ни позволява
отново да обходим завършения итератор, а също така свободно да използваме итератор на
празна колекция която би могла да се разглежда като завършена.
Итератор блокове.
Процесът на ръчно създаване на итератор е доста трудоемко занимание, което включва управление на
състояние и преместване по елементите
на колекцията с извикване на MoveNext. С
помощта на итератор блок
имплементирането на итератор
съществено се опростява както се вижда
на листинг 2.
листинг 2 Екранна снимка на IEnumerator реализиран с итератор блок

Илия Попов ИСТ-2 ФК 20251421006

С помощта на итератор блок може да създадем итератори за колекциите или въобще за
външни ресурси като файлове. За целта е достатъчно отворим файла в началото на функцията и
да връщаме прочетените блокове данни с помощта на yield return. Сега би трябвало да стане
ясно защо метода Reset в контракта на итератора е не задължителен. Да си представим че
итераторът връща данни прочетени по мрежата. Как в такъв случай бихме могли да
реализираме метода Reset?
Итератор блокът се преобразува от компилатора на езика С# в краен автомат с няколко състояния,
съответстващи на началното положение на итератора(когато той реферира към първия елемент), на
крайното положение(когато итераторът е обходил всички елементи) и на едно „средно“ положение, при
което той реферира към определен елемент. По този начин итератор блокът представлява сам по себе си
някаква форма на копрограма corouting, която продължава изпълнението си от предходната позиция,
чрез MoveNext.
Ленивост на итераторите
Итераторите, получени са помощта на итератор блок са лениви конструкцията им се изпълнява не в
момента на извикване на метода, а при обхождане на елементите чрез MoveNext. Това води до някои
особености при обработката на грешки, тъй като дори и валидацията на агрументите на метода, връщащ
итератор, ще бъде направена едва в момента на използване на итератора както е показано на листинг 3.

листинг 3 Екранна снимка на IEnumerable
На този принцип са построени голяма част от методите в LINQ(Language Integrated Query), което
позволява да получим сложни заявки без излишни разходи.

Използване на итераторите в цикъла foreach
Цикъла foreach е универсален инструмент за обработка на колекции с последователности. Начинът за
неговото преобразуване от компилатора зависи от типа на обхожданата колекция(генерична,
негенерична) и сам по себе си представлява прост цикъл while. За поддръжката на цикъл foreach не е
задължително наличието на интерфейса IEnumerable/IEnumerable<T>. Достатъчно е класът на колекцията
да съдържа метода GetEnumerator, който да връща тип bool чрез MoveNext и свойството Current. Също
така ще обърнем внимание на това че променливата current след версия 5.0 е пренесена във вътрешната
област на видимост. Всеки типизиран итератор имплементира интерфейса IDisposable, тъй като самият
интерфейс IEnumerator<Т> наследява IDisposable. Причината за това е че итераторите получени с
помощта на итератор блок могат да съдържат ресурси които се освобождават в finally блок, чието
извикване се осъществява именно посредством извикването на Dispose на итератора. Но цялата работа се
състои в това че итератор блокът може да връща не само типизиран итератор, но и неговите
предшественици IEnumerable/ IEnumerator, които не имплементират интерфейса IDisposable.

Илия Попов ИСТ-2 ФК 20251421006


Итератори и генератори
Итератор блоковете могат да се използват за създаване на итератори, тоест за обхождане на някакъв
агрегат в паметта(колекции) или извън пределите и (итератор на съдържанието на файл). Но освен това
итератор блокът може да се използва и за създаване на генератори. Пример за прост безкраен генератор
на числата на фибоначи е представен в листинг 4.

листинг 4 Екранна снимка
В този план итератор блокът в С# напомня по общите концепции от другите езици за програмиране,
наречени обхващане на списък, които са предназначени за създаването на последователности и
колекции.

Валидност на итераторите
В някои езици, като например С++ понятието за инвалидация на итераторите(когато итераторът на
колекция стане недействителен) е определено в спецификацията на езика в раздела, посветен на
конкретната колекция. Така например, не за всички контейнери операцията добавяне на елемент прави
итератора недействителен добавянето на елемент в двойно свързан списък е напълно допустимо, а
добавянето на елемент във вектор не. Подобни правила, макар и не толкова формални, съществуват и за
колекциите в .NET Framework. По точно има само едно правило, и то не е свързано с конкретен тип
колекция при промяна на колекцията всички по рано получени итератори стават недействителни. Това
поведение коренно се различава от правилата за колекциите в езика С++ тъй като в случая std::list и двете
приведени операции биха били допустими.

Илия Попов ИСТ-2 ФК 20251421006

Итератори и структури
Итераторите на всички колекции в .NET Framework са изменяеми структури. Това от една страна ни
избавя от допълнително заделяне на място в динамичната при обхождането на колекция, но от друга
може да доведе до неочаквани резултати. Но въпреки потенциалната опасност, итераторите на всяка
широко използвана колекция трябва да бъдат структури. Нещо повече, в някои случаи съществуват
правила забраняващи използването на колекция с класове итератори.

Push-based итератори
В книгата „бандата на четиримата“ са определени два вида итератори, външни и вътрешни, в зависимост
от това кой управлява итерациите клиентът или самият итерато

Преглед на първите от 7 страници - останалите след изтегляне

Описание

Поведенчески шаблон Итератор(Iterator) реализиран за .NET Framework чрез езика C#. UML Диаграмите са реализирани чрез Umbrelo версия 2.32. Дисциплина: Разработване на ООП със софтуерни шаблони

0 коментара

Все още няма коментари. Бъдете първият, който ще коментира.

За да коментирате, трябва да сте влезли в профила си.

Влезте