Menu
Coddy logo textTech

Implementing Interfaces

Part of the Object Oriented Programming section of Coddy's C journey — lesson 41 of 61.

Now that you understand what an interface is — a struct containing only function pointers — it's time to create concrete implementations that fulfill that contract.

Recall our ILogger interface from the previous lesson:

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

typedef struct {
    LogFunc log;
} ILogger;

To implement this interface, we write actual functions that match the LogFunc signature, then create ILogger instances with those functions assigned:

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

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

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

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

Each "constructor" function returns an ILogger with a different function wired in. Code that uses these loggers doesn't need to know which implementation it received:

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

The do_work function is completely decoupled from the logging implementation. You can swap loggers freely without changing any of the core logic — that's the power of programming to an interface.

challenge icon

Challenge

Easy

Let's build a INotifier system that demonstrates how different implementations can fulfill the same interface contract. You'll create concrete notifiers — an EmailNotifier and an SMSNotifier — that both conform to a common interface but produce different outputs.

You'll organize your code across three files:

  • notifier.h: Define the interface here. Create a function pointer type called NotifyFunc that takes a const char* message and returns nothing. Then define an INotifier struct containing only a notify function pointer of this type. Also declare two constructor functions: create_email_notifier and create_sms_notifier, each returning an INotifier by value.
  • notifier.c: Implement the concrete notification functions and constructors here. Create:
    • email_notify — prints the message in the format: [EMAIL] message
    • sms_notify — prints the message in the format: [SMS] message
    • create_email_notifier — returns an INotifier wired to email_notify
    • create_sms_notifier — returns an INotifier wired to sms_notify
  • main.c: Bring everything together here. Read a notification type and a message, create the appropriate notifier using the constructor functions, and send the notification through the interface.

Your program will receive two inputs: a notification type (1 for email, 2 for SMS) and a message to send.

Use the constructor functions to create the appropriate notifier, then call notify through the interface to display the message.

Example output when type is 1 and message is Meeting at 3pm:

[EMAIL] Meeting at 3pm

Example output when type is 2 and message is Your code shipped:

[SMS] Your code shipped

The power of this pattern is that main.c doesn't need to know how each notifier works internally — it simply calls the interface's notify function, and the correct implementation executes. Remember to use include guards in your header file.

Cheat sheet

To implement an interface in C, create concrete functions that match the function pointer signature, then assign them to interface instances.

Define the interface with function pointers:

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

typedef struct {
    LogFunc log;
} ILogger;

Create concrete implementations:

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

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

Use constructor functions to create interface instances:

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

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

Code using the interface is decoupled from specific implementations:

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;
}

Try it yourself

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

int main() {
    int type;
    char message[256];
    
    // Read the notification type (1 for email, 2 for SMS)
    scanf("%d", &type);
    getchar(); // consume newline
    
    // Read the message
    fgets(message, sizeof(message), stdin);
    // Remove trailing newline if present
    int len = 0;
    while (message[len] != '\0') len++;
    if (len > 0 && message[len-1] == '\n') message[len-1] = '\0';
    
    // TODO: Create the appropriate notifier based on type
    // Use create_email_notifier() for type 1
    // Use create_sms_notifier() for type 2
    
    // TODO: Call the notify function through the interface
    // notifier.notify(message);
    
    return 0;
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming