Концепция интерфейсов
Часть раздела Object Oriented Programming путешествия по C на Coddy — урок 40 из 61.
До сих пор в наших структурах данные и поведение были смешаны — например, имя вместе с указателем на функцию. Но что, если бы структура содержала только указатели на функции? Это создает нечто мощное: интерфейс.
Интерфейс определяет, какие операции доступны, без указания того, как они работают. Это контракт, который гласит: «любой объект, реализующий этот интерфейс, должен предоставлять эти функции».
typedef void (*LogFunc)(const char* message);
typedef struct {
LogFunc log;
} ILogger;Структура ILogger не содержит ничего, кроме указателя на функцию. У неё нет собственных данных — она чисто описывает поведение. Префикс «I» — это общепринятое соглашение, указывающее на то, что это интерфейс.
Любой код, который принимает ILogger, не заботится о том, куда направляется лог-сообщение. Оно может выводиться в консоль, записываться в файл или отправляться по сети. Вызывающая сторона просто знает, что она может вызвать log:
void process(ILogger* logger, const char* msg) {
logger->log(msg); // Мы не знаем, КАК оно логирует
}Это разделение является сутью полиморфизма. Функция process работает с любой реализацией логгера — вы можете менять поведение, не изменяя код функции. В следующем уроке вы создадите конкретные реализации, которые соответствуют этому контракту интерфейса.
Задание
ЛегкоДавайте создадим интерфейс ICalculator — структуру, которая содержит только указатели на функции, определяя, какие операции должен поддерживать любой калькулятор, не уточняя, как именно они работают.
Для организации кода вы создадите два файла:
calculator.h: Определите здесь свой интерфейс. Создайте тип указателя на функцию с именемBinaryOp, который принимает два параметра типаintи возвращаетint. Затем определите структуруICalculator, которая содержит только два указателя на функции:operate(типаBinaryOp) иdescribe(указатель на функцию, которая не принимает параметров и ничего не возвращает). Эта структура является чисто интерфейсом — она не хранит данных, только поведение.main.c: Подключите заголовочный файл и реализуйте конкретные функции, которые могут соответствовать интерфейсу:add— возвращает сумму двух целых чиселmultiply— возвращает произведение двух целых чиселdescribe_add— выводитAdditiondescribe_multiply— выводитMultiplication
ICalculator, настройте его в зависимости от входных данных и используйте его для выполнения вычисления.
Ваша программа получит три входных значения: два целых числа и код операции (1 для сложения, 2 для умножения).
В зависимости от кода операции, свяжите ваш ICalculator с соответствующей парой функций. Затем вызовите describe через интерфейс, чтобы вывести название операции, а затем вызовите operate, чтобы вычислить и вывести результат.
Пример вывода, когда входные данные 5, 3 и 1:
Addition
8Пример вывода, когда входные данные 4, 7 и 2:
Multiplication
28Ключевая идея заключается в том, что ваша структура ICalculator определяет контракт — любой код, использующий её, знает, что он может вызвать describe и operate, не зная, какие именно функции подключены. Не забудьте использовать include guards в заголовочном файле.
Шпаргалка
Интерфейс — это структура, которая содержит только указатели на функции, определяя, какие операции доступны, не уточняя, как они работают.
Определение интерфейса:
typedef void (*LogFunc)(const char* message);
typedef struct {
LogFunc log;
} ILogger;Префикс «I» — это общепринятое соглашение, указывающее на то, что это интерфейс. Структура не содержит данных — она описывает исключительно поведение.
Использование интерфейса:
void process(ILogger* logger, const char* msg) {
logger->log(msg); // Мы не знаем, КАК происходит логирование
}Такое разделение обеспечивает полиморфизм — функция работает с любой реализацией интерфейса, позволяя менять поведение без изменения кода самой функции.
Попробуйте сами
#include <stdio.h>
#include "calculator.h"
// TODO: Реализовать функцию сложения
// Возвращает сумму двух целых чисел
// TODO: Реализовать функцию умножения
// Возвращает произведение двух целых чисел
// TODO: Реализовать функцию describe_add
// Выводит "Addition"
// TODO: Реализовать функцию describe_multiply
// Выводит "Multiplication"
int main() {
int num1, num2, op_code;
scanf("%d", &num1);
scanf("%d", &num2);
scanf("%d", &op_code);
// TODO: Создать экземпляр ICalculator
// TODO: В зависимости от op_code (1 для сложения, 2 для умножения),
// подключить к калькулятору соответствующие функции
// TODO: Вызвать describe через интерфейс
// TODO: Вызвать operate через интерфейс и вывести результат
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы модульного программирования
Заголовочные файлыСтражи включенияИсходные файлыСтатические функцииПовторение: Модульный калькулятор4Инкапсуляция
Концепция непрозрачных указателейОпределение непрозрачных структурГеттеры и сеттерыВалидация в сеттерахИтоги: Секретный ящик2Объекты и методы
Структуры как объектыУказатель 'Self'Константная корректностьУказатель против значенияВспомогательные методыИтоги: Point Manager5Проект: Простой банковский счет
Настройка проектаРеализация счета8Полиморфизм
Указатели на функции в структурахСимуляция методовКонцепция интерфейсовРеализация интерфейсовПолиморфная итерацияПовторение: Greeter3Жизненный цикл объекта
Паттерн «Конструктор»Паттерн «Деструктор»Инициализация в стекеГлубокое копированиеПовторение: String Wrapper6Наследование через композицию
Встраивание структурПравило первого элементаДоступ к элементам родителяUpcastingПовторение: Иерархия фигур9Проект: Рисование фигур
Обзор проектаРеализация круга