Итерация или перебор некоторого набора объектов является довольно частой задачей при работе с наборами данных. Если набор данных представляет структуру данных с произвольным доступом, например, массив, то мы можем использовать индексы для получения конкретных элементов набора. Проблема может возникнуть, когда мы захотим изменить структуру данных. Например, мы хранили строки, а потом решили, что каждый элемент будет представляеть структуру и будет хранить две строки. Если внешний код использовал этот набор, то изменение структуры данных потребует изменения внешнего кода, который обращается к этим данным.
Другая проблема можем заключаться в нежелательном доступе к данным. Например, какждый элемент в массиве представляет структуру с кучей полей. Но мы хотим, чтобы внешний код имел доступ только к некоторым из этих полей, и чтобы реализация структуры данных была скрыта от внешнего кода. Но при этом необходимо, что внешний код имел удобный доступ к данным. К тому же внешнему коду может быть интересная только какие-то конкретные поля структуры, а не вся структура в целом. Поэтому возвращение всей структуры, которая представляет данные, будет избыточно.
При работе со структурами данных с произвольным доступом типовым решением является определение функции, которая принимает индекс объекта в наборе и возвращает данные объекта. В общем случае это выглядит так:
/* API модуля - функция принимает индекс элемента и возвращает некоторые данные */ int getX(int index); /* Реализация API модуля */ #define MAX_ELEMENTS 4 // структура, которая представляет данные struct Data { int x; int y; }; // сами данные static struct Data elements[MAX_ELEMENTS]={{1,2},{3,4},{5,6},{7,8}}; // функция для доступа к данным по индексу int getX(int index) { return elements[index].x; } /* Код клиента, который использует API */ int x1 = getX(1); int x2 = getX(2);
Благодаря функции-абстракции над обращением к элементу по индексу мы можем предоставить клиенту только те данные, которые ему нужны и к которым он может иметь доступ. А если в будущем структура данных измениться, на стороне клиента код не изменится. Данный паттерн еще называют External Iterator или внешний итератор.
Рассмотрим небольшой пример. Пусть в заголовочном файле account.h у нас определен следующий API:
#define ACCOUNT_NUMBER 200 char* getUserName(size_t index);
Константа ACCOUNT_NUMBER представляет максимальное количество данных - учетных данных пользователей, а функция getUserName возвращает имя пользователя по определенному индексу.
В файле account.c определим реализацию API:
#include <stdio.h> #include "account.h" #define NAME_SIZE 20 #define PASSWORD_SIZE 20 // структура данных struct ACCOUNT { char login[NAME_SIZE]; char password[PASSWORD_SIZE]; }; // типовые данные static struct ACCOUNT accounts[ACCOUNT_NUMBER] = { {"Tom", "qwerty"}, {"Bob", "123456"}, {"Sam", "6589967"}, {"Tim", "eruiyiu"} }; // возвращаем логин char* getUserName(size_t index) { return accounts[index].login; }
Здесь данные представлены структурой Account, которая хранит условно имя пользователя/логин и его пароль. И также определен типовой набор данных - массив accounts. Пароль в этой структуре может представлять такие данные, которые не должны быть видны извне, поэтому определяем функционал только для возвращения логина с помщью функции getUserName, которая получает индекс пользователя в массиве accounts.
Определим основной файл программы - app.c, в котором будем обращаться к данным:
#include <stdio.h> #include "account.h" int main(void) { for(size_t i=0; i < ACCOUNT_NUMBER; i++) { char* username = getUserName(i); if(username[0] == 'T') { printf("%s\n", username); } } }
Здесь проходим по всем элементам и получаем имя каждого пользователя с помощью функции getUserName. Для примера выводим на консоль все имена пользователей, которые начинаются на "T".
Данный паттерн позволяет абстрагироваться от строения структуры данных и обеспечить некоторую безопасность от несанкционированного доступа. Однако минусом подхода - он не очень удобен при работе с последовательными структурами данных типа связанных списков, где может потребоваться динамическое добавление или удаление элементов.