Menu
Coddy logo textTech

Реализация интерфейсов

Часть раздела 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 полностью отделена от реализации логирования. Вы можете свободно менять логгеры, не изменяя основную логику — в этом и заключается преимущество программирования на уровне интерфейсов.

challenge icon

Задание

Легко

Давайте создадим систему INotifier, которая демонстрирует, как различные реализации могут соответствовать одному и тому же контракту интерфейса. Вы создадите конкретные уведомляторы — EmailNotifier и SMSNotifier — которые оба соответствуют общему интерфейсу, но выдают разные результаты.

Вы организуете свой код в трех файлах:

  • notifier.h: Определите здесь интерфейс. Создайте тип указателя на функцию с именем NotifyFunc, который принимает сообщение const char* и ничего не возвращает. Затем определите структуру INotifier, содержащую только указатель на функцию notify этого типа. Также объявите две функции-конструктора: create_email_notifier и create_sms_notifier, каждая из которых возвращает INotifier по значению.
  • notifier.c: Реализуйте здесь конкретные функции уведомления и конструкторы. Создайте:
    • email_notify — выводит сообщение в формате: [EMAIL] message
    • sms_notify — выводит сообщение в формате: [SMS] message
    • create_email_notifier — возвращает INotifier, связанный с email_notify
    • create_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;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Object Oriented Programming