Метафора
Все помнят школьное «на первый второй рассчитайся!»? Вот именно в этот момент шеренга вашего класса и являлась реализацией паттерна «итератор», хотя в программировании это конечно более функциональное понятие, но суть примерно та же. «Итератор» предоставляет правила доступа к списку каких-либо объектов независимо от того, что это за объекты. То есть не важно, какой именно класс построен и из каких учеников, должны быть общие правила подсчета и обращения как каждому ученику по списку, вроде «13-ый, выйти из строя». Ссылки, которые вы видите на многих сайтах для переходов по страницам, вроде «следующая», «предыдущая», «в начало» и т.п. по своей сути также являются доступом «итератору» который отвечает за страницы сайта.
Назначение
Паттерн предоставляет способ последовательного доступа ко всем элементам составного объекта, не раскрывая его внутреннего представления. Итератор предоставляет абстрактный интерфейс для доступа к содержимому составных объектов, не раскрывая клиентам их внутреннюю структуру.
Преимущества:
- Поддерживает различные способы перебора агрегата, одновременно могут быть активны несколько переборов.
Диаграмма
Клиент работает с абстрактным интерфейсом итератора, реализацию которого, получает от конкретного составного объекта. Конкретный итератор следит за текущей позицией в конкретном агрегате.
Пример
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
*&---------------------------------------------------------------------* *& Определение объектов / интерфейсов *&---------------------------------------------------------------------* INTERFACE lif_iterator. METHODS: first RETURNING VALUE(rs_spfli) TYPE spfli, next RETURNING VALUE(rs_spfli) TYPE spfli, is_done RETURNING VALUE(rv_done) TYPE abap_bool, current RETURNING VALUE(rs_spfli) TYPE spfli. ENDINTERFACE. CLASS lcl_aggregate DEFINITION FRIENDS lif_iterator. PUBLIC SECTION. METHODS: create_iterator RETURNING VALUE(ro_iterator) TYPE REF TO lif_iterator, constructor. PRIVATE SECTION. DATA: mt_data TYPE STANDARD TABLE OF spfli. ENDCLASS. CLASS lcl_iterator DEFINITION. PUBLIC SECTION. INTERFACES: lif_iterator. ALIASES: first FOR lif_iterator~first, next FOR lif_iterator~next, is_done FOR lif_iterator~is_done, current FOR lif_iterator~current. METHODS: constructor IMPORTING io_aggregate TYPE REF TO lcl_aggregate. PRIVATE SECTION. DATA: mv_position TYPE i, mv_lines TYPE i, mo_aggregate TYPE REF TO lcl_aggregate. ENDCLASS. *&---------------------------------------------------------------------* *& Реализация *&---------------------------------------------------------------------* CLASS lcl_aggregate IMPLEMENTATION. METHOD constructor. SELECT * FROM spfli INTO CORRESPONDING FIELDS OF TABLE mt_data. ENDMETHOD. METHOD create_iterator. CREATE OBJECT ro_iterator TYPE lcl_iterator EXPORTING io_aggregate = me. ENDMETHOD. ENDCLASS. CLASS lcl_iterator IMPLEMENTATION. METHOD constructor. mo_aggregate = io_aggregate. mv_lines = lines( mo_aggregate->mt_data ). mv_position = 0. ENDMETHOD. METHOD first. mv_position = 1. READ TABLE mo_aggregate->mt_data INDEX mv_position INTO rs_spfli. ENDMETHOD. METHOD next. mv_position = mv_position + 1. IF mv_position > mv_lines. mv_position = mv_position - 1. RETURN. ENDIF. READ TABLE mo_aggregate->mt_data INDEX mv_position INTO rs_spfli. ENDMETHOD. METHOD is_done. IF ( mv_position + 1 ) > mv_lines. rv_done = abap_true. ENDIF. ENDMETHOD. METHOD current. READ TABLE mo_aggregate->mt_data INDEX mv_position INTO rs_spfli. ENDMETHOD. ENDCLASS. *&---------------------------------------------------------------------* *& Работа с шаблоном *&---------------------------------------------------------------------* START-OF-SELECTION. DATA: lo_aggregate TYPE REF TO lcl_aggregate, lo_iterator TYPE REF TO lif_iterator, ls_spfli TYPE spfli. CREATE OBJECT lo_aggregate. lo_iterator = lo_aggregate->create_iterator( ). WHILE lo_iterator->is_done( ) <> abap_true. ls_spfli = lo_iterator->next( ). WRITE: / sy-index, ls_spfli-connid, ls_spfli-carrid. ENDWHILE. |
В данном примере мы сделали простейший итератор по таблице из объекта lcl_aggregate. Последовательность доступа к таблице относительно текущей позиции в итераторе.
Итераторы широко используются в библиотеках работы с XML, например, в iXML для обхода DOM:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
CLASS ixml_demo DEFINITION. PUBLIC SECTION. CLASS-METHODS main. ENDCLASS. CLASS ixml_demo IMPLEMENTATION. METHOD main. DATA(out) = cl_demo_output=>new( )->begin_section( `XML-Data` ). DATA(xml) = `<?xml version="1.0"?>` && `<order number="4711"` && ` xmlns:demo="http://www.sap.com/abapdemos">` && `<!-- Head and body of order -->` && ` <demo:head>` && ` <demo:status>confirmed</demo:status>` && ` <demo:date format="mm-dd-yyyy">07-19-2012</demo:date>` && ` </demo:head>` && ` <demo:body>` && ` <demo:item units="2" price="17.00">Part No. 0110</demo:item>` && ` <demo:item units="1" price="10.50">Part No. 1609</demo:item>` && ` <demo:item units="5" price="12.30">Part No. 1710</demo:item>` && ` </demo:body>` && `</order>`. out->write_xml( xml ). out->next_section( `XML-Document in DOM-Format` ). TYPES: BEGIN OF t_node, gid TYPE i, type TYPE i, prefix TYPE string, name TYPE string, value TYPE string, END OF t_node. DATA node_tab TYPE STANDARD TABLE OF t_node. DATA(ixml) = cl_ixml=>create( ). DATA(document) = ixml->create_document( ). TRY. CALL TRANSFORMATION id SOURCE XML xml RESULT XML document. CATCH cx_transformation_error. RETURN. ENDTRY. DATA(iterator) = document->create_iterator( ). DO. DATA(node) = iterator->get_next( ). IF node IS INITIAL. EXIT. ENDIF. APPEND VALUE #( gid = node->get_gid( ) type = node->get_type( ) prefix = node->get_namespace_prefix( ) name = node->get_name( ) value = node->get_value( ) ) TO node_tab. ENDDO. out->write_data( node_tab ). TYPES: BEGIN OF t_attribute, name TYPE string, value TYPE string, END OF t_attribute. DATA attribute_tab TYPE STANDARD TABLE OF t_attribute. iterator->reset( ). DO. node = iterator->get_next( ). IF node IS INITIAL. EXIT. ENDIF. DATA(attributes) = node->get_attributes( ). IF attributes IS INITIAL OR attributes->get_length( ) = 0. CONTINUE. ENDIF. CLEAR attribute_tab. DO. DATA(attribute) = attributes->get_item( sy-index - 1 ). IF attribute IS INITIAL. EXIT. ENDIF. APPEND VALUE #( name = attribute->get_name( ) value = attribute->get_value( ) ) TO attribute_tab. ENDDO. out->begin_section( |Attributes of GID { node->get_gid( ) }| )->write_data( attribute_tab )->end_section( ). ENDDO. out->display( ). ENDMETHOD. ENDCLASS. START-OF-SELECTION. ixml_demo=>main( ). |
Еще один пример итератора из стандарта — cl_object_collection:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
data: lv_result TYPE float. data(lo_container) = new cl_object_collection( ). data(lo_circle) = new zcl_circle( 1 ). lo_container->add( lo_circle ). data(lo_circle2) = new zcl_circle( 1 ). lo_container->add( lo_circle2 ). data(lo_rectangle) = new zcl_rectangle( iv_width = 1 iv_height = 2 ). lo_container->add( lo_rectangle ). data(lo_iterator) = lo_container->get_iterator( ). WHILE lo_iterator->has_next( ). data(lo_shape) = cast ZIF_SHAPE( lo_iterator->get_next( ) ). lv_result = lv_result + lo_shape->get_area( ). ENDWHILE. |
Михаил, я правильно понимаю, что в реальной жизни create_iterator() из первого примера должен принимать на вход какой-либо параметр, в зависимости от которого будет возвращаться экземпляр того или иного итератора? Просто в данном примере для краткости ты создаешь его один?
И еще, не противоречит ли возвращение именно значения строки из итератора, а не ссылки на нее (это я про first(), next() и current()?
А вообще по факту в ABAP, можно сказать, используется паттерн итератор в виде вторичных ключей внутренних таблиц…
Привет! Суть тут не столько во множестве разных реализаций итераторов, сколько в универсальном обходе некоторого составного объекта. В примере же, возвращается строка таблицы исключительно ради простоты восприятия. Обычно под составными объектом понимают объект, состоящий из множества других объектов (как в следующем примере с xml узлом).
Угу, понял. Почему-то думал, что основное направление именно работа со структурированными данными. Что-то вроде: создаешь несколько итераторов с разными критериями сортировки и затем пользуешься. Я просто больше на примерах списков различных для web его смотрел…
Кстати, как же все таки лаконично смотрится код на 7.40 (:
Тут главное с лаконичностью не переборщить))