Menu
Coddy logo textTech

Implementando interfaces

Parte de la sección Object Oriented Programming del Journey de C de Coddy — lección 41 de 61.

Ahora que entiendes qué es una interfaz — un struct que contiene solo punteros a funciones — es hora de crear implementaciones concretas que cumplan con ese contrato.

Recordemos nuestra interfaz ILogger de la lección anterior:

typedef void (*LogFunc)(const char* message);

typedef struct {
    LogFunc log;
} ILogger;

Para implementar esta interfaz, escribimos funciones reales que coincidan con la firma LogFunc, luego creamos instancias de ILogger con esas funciones asignadas:

void console_log(const char* message) {
    printf("[CONSOLE] %s\n", message);
}

void file_log(const char* message) {
    printf("[FILE] %s\n", message);  // Simulando la salida de archivo
}

ILogger create_console_logger() {
    ILogger logger = { console_log };
    return logger;
}

ILogger create_file_logger() {
    ILogger logger = { file_log };
    return logger;
}

Cada función "constructor" devuelve un ILogger con una función diferente conectada. El código que utiliza estos loggers no necesita saber qué implementación recibió:

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);  // Usa console_log
    do_work(&file);     // Usa file_log
    return 0;
}

La función do_work está completamente desacoplada de la implementación de registro. Puedes intercambiar los loggers libremente sin cambiar nada de la lógica central — ese es el poder de programar hacia una interfaz.

challenge icon

Desafío

Fácil

Vamos a construir un sistema INotifier que demuestre cómo diferentes implementaciones pueden cumplir con el mismo contrato de interfaz. Crearás notificadores concretos — un EmailNotifier y un SMSNotifier — que cumplen con una interfaz común pero producen salidas diferentes.

Organizarás tu código en tres archivos:

  • notifier.h: Define la interfaz aquí. Crea un tipo de puntero a función llamado NotifyFunc que reciba un mensaje const char* y no devuelva nada. Luego, define una estructura INotifier que contenga solo un puntero a función notify de este tipo. También declara dos funciones constructoras: create_email_notifier y create_sms_notifier, cada una devolviendo un INotifier por valor.
  • notifier.c: Implementa las funciones de notificación concretas y los constructores aquí. Crea:
    • email_notify — imprime el mensaje en el formato: [EMAIL] message
    • sms_notify — imprime el mensaje en el formato: [SMS] message
    • create_email_notifier — devuelve un INotifier vinculado a email_notify
    • create_sms_notifier — devuelve un INotifier vinculado a sms_notify
  • main.c: Reúne todo aquí. Lee un tipo de notificación y un mensaje, crea el notificador apropiado utilizando las funciones constructoras y envía la notificación a través de la interfaz.

Tu programa recibirá dos entradas: un tipo de notificación (1 para email, 2 para SMS) y un mensaje para enviar.

Usa las funciones constructoras para crear el notificador apropiado, luego llama a notify a través de la interfaz para mostrar el mensaje.

Ejemplo de salida cuando el tipo es 1 y el mensaje es Meeting at 3pm:

[EMAIL] Meeting at 3pm

Ejemplo de salida cuando el tipo es 2 y el mensaje es Your code shipped:

[SMS] Your code shipped

El poder de este patrón es que main.c no necesita saber cómo funciona internamente cada notificador — simplemente llama a la función notify de la interfaz, y se ejecuta la implementación correcta. Recuerda usar protectores de inclusión (include guards) en tu archivo de cabecera.

Hoja de referencia

Para implementar una interfaz en C, cree funciones concretas que coincidan con la firma del puntero de función y luego asígnelas a las instancias de la interfaz.

Defina la interfaz con punteros de función:

typedef void (*LogFunc)(const char* message);

typedef struct {
    LogFunc log;
} ILogger;

Cree implementaciones concretas:

void console_log(const char* message) {
    printf("[CONSOLE] %s\n", message);
}

void file_log(const char* message) {
    printf("[FILE] %s\n", message);
}

Utilice funciones constructoras para crear instancias de la interfaz:

ILogger create_console_logger() {
    ILogger logger = { console_log };
    return logger;
}

ILogger create_file_logger() {
    ILogger logger = { file_log };
    return logger;
}

El código que utiliza la interfaz está desacoplado de las implementaciones específicas:

void do_work(ILogger* logger) {
    logger->log("Starting work...");
}

int main() {
    ILogger console = create_console_logger();
    ILogger file = create_file_logger();
    
    do_work(&console);  // Uses console_log
    do_work(&file);     // Uses file_log
    return 0;
}

Pruébalo tú mismo

#include <stdio.h>
#include "notifier.h"

int main() {
    int type;
    char message[256];
    
    // Lee el tipo de notificación (1 para email, 2 para SMS)
    scanf("%d", &type);
    getchar(); // consume el salto de línea
    
    // Lee el mensaje
    fgets(message, sizeof(message), stdin);
    // Elimina el salto de línea final si está presente
    int len = 0;
    while (message[len] != '\0') len++;
    if (len > 0 && message[len-1] == '\n') message[len-1] = '\0';
    
    // TODO: Crear el notificador apropiado basado en el tipo
    // Usa create_email_notifier() para el tipo 1
    // Usa create_sms_notifier() para el tipo 2
    
    // TODO: Llamar a la función notify a través de la interfaz
    // notifier.notify(message);
    
    return 0;
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Object Oriented Programming