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
EasyLet'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 calledNotifyFuncthat takes aconst char*message and returns nothing. Then define anINotifierstruct containing only anotifyfunction pointer of this type. Also declare two constructor functions:create_email_notifierandcreate_sms_notifier, each returning anINotifierby value.notifier.c: Implement the concrete notification functions and constructors here. Create:email_notify— prints the message in the format:[EMAIL] messagesms_notify— prints the message in the format:[SMS] messagecreate_email_notifier— returns anINotifierwired toemail_notifycreate_sms_notifier— returns anINotifierwired tosms_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 3pmExample output when type is 2 and message is Your code shipped:
[SMS] Your code shippedThe 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;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Modular Programming Basics
Header FilesInclude GuardsSource FilesStatic FunctionsRecap: Modular Calculator4Encapsulation
Opaque Pointers ConceptDefining Opaque StructsGetters and SettersValidation in SettersRecap: Secret Box2Objects and Methods
Structs as ObjectsThe 'Self' PointerConst CorrectnessPointer vs ValueHelper MethodsRecap: Point Manager5Project: Simple Bank Account
Project SetupImplementation of Account8Polymorphism
Function Pointers in StructsSimulating MethodsThe Interface ConceptImplementing InterfacesPolymorphic IterationRecap: Greeter3Object Lifecycle
Constructor PatternDestructor PatternStack InitializationDeep CopyRecap: String Wrapper6Inheritance via Composition
Struct EmbeddingThe First Member RuleAccessing Parent MembersUpcastingRecap: Shape Hierarchy