Полиморфная итерация
Часть раздела Object Oriented Programming путешествия по C на Coddy — урок 42 из 61.
Вы научились создавать интерфейсы и реализовывать их с помощью различных конкретных функций. Настоящая выгода проявляется тогда, когда вы можете единообразно работать с коллекцией различных объектов — перебирать их и вызывать один и тот же указатель на функцию для каждого из них, независимо от того, какое именно поведение в них заложено.
Представьте, что у вас есть массив структур Action, каждая из которых имеет свою собственную назначенную функцию execute:
typedef void (*ExecuteFunc)(void);
typedef struct {
const char* name;
ExecuteFunc execute;
} Action;
void jump(void) { printf("Jumping!\n"); }
void run(void) { printf("Running!\n"); }
void rest(void) { printf("Resting...\n"); }Вы можете создать массив этих действий, каждое из которых привязано к отдельной функции, а затем перебрать их с помощью простого цикла for:
int main() {
Action actions[3] = {
{ "Jump", jump },
{ "Run", run },
{ "Rest", rest }
};
for (int i = 0; i < 3; i++) {
printf("%s: ", actions[i].name);
actions[i].execute();
}
return 0;
}Цикл не знает и не заботится о том, что каждое действие делает внутри. Он просто вызывает execute() для каждого элемента.
Это полиморфизм в действии — один и тот же код обрабатывает объекты с совершенно разным поведением. Добавление нового типа действия не требует изменений в логике итерации; вы просто добавляете еще один элемент в массив.
Задание
ЛегкоДавайте создадим систему управления Task, которая демонстрирует полиморфную итерацию — обработку коллекции объектов, имеющих общий интерфейс, но выполняющих разные действия.
Вы организуете свой код в трех файлах:
task.h: Определите здесь интерфейс задачи. Создайте тип указателя на функцию с именемTaskFunc, который не принимает параметров и ничего не возвращает. Затем определите структуруTask, содержащуюname(типаconst char*) и указатель на функциюrunтипаTaskFunc.task.c: Реализуйте три различные функции задач, которые представляют разные виды работ:backup_task— выводитBacking up data...cleanup_task— выводитCleaning up files...report_task— выводитGenerating report...
main.c: Соберите все вместе здесь. Создайте массив структурTask, каждая из которых связана с соответствующей функцией задачи. Затем выполните итерацию по массиву и выполните каждую задачу полиморфно — для каждой задачи выведите ее имя, а затем вызовите ее функциюrun.
Ваша программа получит на вход одно значение: количество задач для запуска (1, 2 или 3).
Создайте массив, содержащий ровно три задачи в следующем порядке: задача резервного копирования с именем Backup, задача очистки с именем Cleanup и задача отчета с именем Report. На основе введенного количества выполните итерацию только по этому количеству задач, начиная с начала массива.
Для каждой задачи в итерации выведите имя задачи, за которым следует двоеточие и пробел, а затем вызовите ее функцию run.
Пример вывода, если на вход подано 2:
Backup: Backing up data...
Cleanup: Cleaning up files...Пример вывода, если на вход подано 3:
Backup: Backing up data...
Cleanup: Cleaning up files...
Report: Generating report...Ключевая идея заключается в том, что вашему циклу не нужно знать, что делает каждая задача — он просто выводит имя и вызывает run() для каждого элемента. Один и тот же код итерации единообразно обрабатывает все типы задач. Не забудьте использовать include guards в заголовочном файле.
Шпаргалка
Вы можете перебирать коллекции объектов, имеющих общий интерфейс, вызывая один и тот же указатель на функцию для каждого элемента, независимо от его конкретной реализации. Это и есть полиморфизм — единообразная работа с различными объектами.
Определите тип указателя на функцию и структуру, которая его использует:
typedef void (*ExecuteFunc)(void);
typedef struct {
const char* name;
ExecuteFunc execute;
} Action;Создайте конкретные функции, соответствующие сигнатуре:
void jump(void) { printf("Jumping!\n"); }
void run(void) { printf("Running!\n"); }
void rest(void) { printf("Resting...\n"); }Создайте массив структур, каждая из которых связана с определенной функцией, а затем переберите их в цикле:
Action actions[3] = {
{ "Jump", jump },
{ "Run", run },
{ "Rest", rest }
};
for (int i = 0; i < 3; i++) {
printf("%s: ", actions[i].name);
actions[i].execute();
}Циклу не нужно знать, что именно делает каждое действие внутри — он просто вызывает указатель на функцию для каждого элемента. Добавление новых типов не требует изменений в логике итерации.
Попробуйте сами
#include <stdio.h>
#include "task.h"
// Объявите функции задач, реализованные в task.c
void backup_task(void);
void cleanup_task(void);
void report_task(void);
int main() {
int count;
scanf("%d", &count);
// TODO: Создайте массив из 3 структур Task в следующем порядке:
// 1. Задача резервного копирования с именем "Backup", использующая backup_task
// 2. Задача очистки с именем "Cleanup", использующая cleanup_task
// 3. Report task named "Report" using report_task
// TODO: Переберите 'count' задач из массива
// Для каждой задачи выведите: "<name>: ", затем вызовите её функцию run
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы модульного программирования
Заголовочные файлыСтражи включенияИсходные файлыСтатические функцииПовторение: Модульный калькулятор4Инкапсуляция
Концепция непрозрачных указателейОпределение непрозрачных структурГеттеры и сеттерыВалидация в сеттерахИтоги: Секретный ящик2Объекты и методы
Структуры как объектыУказатель 'Self'Константная корректностьУказатель против значенияВспомогательные методыИтоги: Point Manager5Проект: Простой банковский счет
Настройка проектаРеализация счета8Полиморфизм
Указатели на функции в структурахСимуляция методовКонцепция интерфейсовРеализация интерфейсовПолиморфная итерацияПовторение: Greeter3Жизненный цикл объекта
Паттерн «Конструктор»Паттерн «Деструктор»Инициализация в стекеГлубокое копированиеПовторение: String Wrapper6Наследование через композицию
Встраивание структурПравило первого элементаДоступ к элементам родителяUpcastingПовторение: Иерархия фигур9Проект: Рисование фигур
Обзор проектаРеализация круга