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.
Desafío
FácilVamos 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 llamadoNotifyFuncque reciba un mensajeconst char*y no devuelva nada. Luego, define una estructuraINotifierque contenga solo un puntero a funciónnotifyde este tipo. También declara dos funciones constructoras:create_email_notifierycreate_sms_notifier, cada una devolviendo unINotifierpor valor.notifier.c: Implementa las funciones de notificación concretas y los constructores aquí. Crea:email_notify— imprime el mensaje en el formato:[EMAIL] messagesms_notify— imprime el mensaje en el formato:[SMS] messagecreate_email_notifier— devuelve unINotifiervinculado aemail_notifycreate_sms_notifier— devuelve unINotifiervinculado asms_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 3pmEjemplo de salida cuando el tipo es 2 y el mensaje es Your code shipped:
[SMS] Your code shippedEl 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;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1Fundamentos de programación modular
Archivos de cabeceraGuardas de inclusiónArchivos fuenteFunciones estáticasRepaso: Calculadora modular4Encapsulamiento
Concepto de punteros opacosDefinición de structs opacosGetters y SettersValidación en settersResumen: Caja secreta7Punteros a funciones
Declaración de punteros a funcionesLlamada a punteros a funcionesTypedef para punteros a funcionesPasar funciones como argumentosRepaso: Dispatch de calculadora10Contenedores genéricos
Repaso de punteros voidWrapper genéricoSwap genéricoComparación genéricaRepaso: Array genérico2Objetos y métodos
Structs como objetosEl puntero 'Self'Const CorrectnessPuntero vs. ValorMétodos auxiliaresResumen: Point Manager5Proyecto: Cuenta bancaria simple
Configuración del proyectoImplementación de la cuenta8Polimorfismo
Punteros a funciones en estructurasSimulando métodosEl concepto de interfazImplementando interfacesIteración polimórficaResumen: Greeter3Ciclo de vida de los objetos
Patrón ConstructorPatrón DestructorInicialización en StackCopia profundaResumen: String Wrapper6Herencia mediante composición
Incrustación de StructsLa regla del primer miembroAcceso a miembros padreUpcastingResumen: Jerarquía de formas9Proyecto: Dibujador de figuras
Descripción general del proyectoImplementación del círculo