Реализация интерфейсов
Часть раздела Object Oriented Programming путешествия по C на Coddy — урок 41 из 61.
Теперь, когда вы понимаете, что такое интерфейс — структура, содержащая только указатели на функции — пришло время создать конкретные реализации, которые соответствуют этому контракту.
Вспомним наш интерфейс ILogger из предыдущего урока:
typedef void (*LogFunc)(const char* message);
typedef struct {
LogFunc log;
} ILogger;Для реализации этого интерфейса мы пишем фактические функции, которые соответствуют сигнатуре LogFunc, а затем создаем экземпляры ILogger с назначенными им функциями:
void console_log(const char* message) {
printf("[CONSOLE] %s\n", message);
}
void file_log(const char* message) {
printf("[FILE] %s\n", message); // Симуляция вывода в файл
}
ILogger create_console_logger() {
ILogger logger = { console_log };
return logger;
}
ILogger create_file_logger() {
ILogger logger = { file_log };
return logger;
}Каждая функция-«конструктор» возвращает ILogger с привязанной другой функцией. Коду, использующему эти логгеры, не нужно знать, какую именно реализацию он получил:
void do_work(ILogger* logger) {
logger->log("Starting work...");
logger->log("Work complete!");
}
int main() {
ILogger console = create_console_logger();
ILogger file = create_file_logger();
do_work(&console); // Использует console_log
do_work(&file); // Использует file_log
return 0;
}Функция do_work полностью отделена от реализации логирования. Вы можете свободно менять логгеры, не изменяя основную логику — в этом и заключается преимущество программирования на уровне интерфейсов.
Задание
ЛегкоДавайте создадим систему INotifier, которая демонстрирует, как различные реализации могут соответствовать одному и тому же контракту интерфейса. Вы создадите конкретные уведомляторы — EmailNotifier и SMSNotifier — которые оба соответствуют общему интерфейсу, но выдают разные результаты.
Вы организуете свой код в трех файлах:
notifier.h: Определите здесь интерфейс. Создайте тип указателя на функцию с именемNotifyFunc, который принимает сообщениеconst char*и ничего не возвращает. Затем определите структуруINotifier, содержащую только указатель на функциюnotifyэтого типа. Также объявите две функции-конструктора:create_email_notifierиcreate_sms_notifier, каждая из которых возвращаетINotifierпо значению.notifier.c: Реализуйте здесь конкретные функции уведомления и конструкторы. Создайте:email_notify— выводит сообщение в формате:[EMAIL] messagesms_notify— выводит сообщение в формате:[SMS] messagecreate_email_notifier— возвращаетINotifier, связанный сemail_notifycreate_sms_notifier— возвращаетINotifier, связанный сsms_notify
main.c: Объедините все здесь. Считайте тип уведомления и сообщение, создайте соответствующий уведомлятор с помощью функций-конструкторов и отправьте уведомление через интерфейс.
Ваша программа получит два входных значения: тип уведомления (1 для email, 2 для SMS) и сообщение для отправки.
Используйте функции-конструкторы для создания соответствующего уведомлятора, затем вызовите notify через интерфейс, чтобы отобразить сообщение.
Пример вывода, когда тип равен 1, а сообщение — Meeting at 3pm:
[EMAIL] Meeting at 3pmПример вывода, когда тип равен 2, а сообщение — Your code shipped:
[SMS] Your code shippedСила этого паттерна заключается в том, что main.c не нужно знать, как каждый уведомлятор работает внутри — он просто вызывает функцию интерфейса notify, и выполняется правильная реализация. Не забудьте использовать include guards в заголовочном файле.
Шпаргалка
Чтобы реализовать интерфейс на языке C, создайте конкретные функции, соответствующие сигнатуре указателя на функцию, а затем назначьте их экземплярам интерфейса.
Определите интерфейс с помощью указателей на функции:
typedef void (*LogFunc)(const char* message);
typedef struct {
LogFunc log;
} ILogger;Создайте конкретные реализации:
void console_log(const char* message) {
printf("[CONSOLE] %s\n", message);
}
void file_log(const char* message) {
printf("[FILE] %s\n", message);
}Используйте функции-конструкторы для создания экземпляров интерфейса:
ILogger create_console_logger() {
ILogger logger = { console_log };
return logger;
}
ILogger create_file_logger() {
ILogger logger = { file_log };
return logger;
}Код, использующий интерфейс, отделен от конкретных реализаций:
void do_work(ILogger* logger) {
logger->log("Starting work...");
}
int main() {
ILogger console = create_console_logger();
ILogger file = create_file_logger();
do_work(&console); // Использует console_log
do_work(&file); // Использует file_log
return 0;
}Попробуйте сами
#include <stdio.h>
#include "notifier.h"
int main() {
int type;
char message[256];
// Считать тип уведомления (1 для email, 2 для SMS)
scanf("%d", &type);
getchar(); // поглотить символ новой строки
// Считать сообщение
fgets(message, sizeof(message), stdin);
// Удалить завершающий символ новой строки, если он есть
int len = 0;
while (message[len] != '\0') len++;
if (len > 0 && message[len-1] == '\n') message[len-1] = '\0';
// TODO: Создать соответствующий notifier на основе type
// Использовать create_email_notifier() для type 1
// Использовать create_sms_notifier() для type 2
// TODO: Вызвать функцию notify через интерфейс
// notifier.notify(message);
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы модульного программирования
Заголовочные файлыСтражи включенияИсходные файлыСтатические функцииПовторение: Модульный калькулятор4Инкапсуляция
Концепция непрозрачных указателейОпределение непрозрачных структурГеттеры и сеттерыВалидация в сеттерахИтоги: Секретный ящик2Объекты и методы
Структуры как объектыУказатель 'Self'Константная корректностьУказатель против значенияВспомогательные методыИтоги: Point Manager5Проект: Простой банковский счет
Настройка проектаРеализация счета8Полиморфизм
Указатели на функции в структурахСимуляция методовКонцепция интерфейсовРеализация интерфейсовПолиморфная итерацияПовторение: Greeter3Жизненный цикл объекта
Паттерн «Конструктор»Паттерн «Деструктор»Инициализация в стекеГлубокое копированиеПовторение: String Wrapper6Наследование через композицию
Встраивание структурПравило первого элементаДоступ к элементам родителяUpcastingПовторение: Иерархия фигур9Проект: Рисование фигур
Обзор проектаРеализация круга