인터페이스 구현하기
Coddy C 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 61개 중 41번째.
이제 인터페이스가 무엇인지 — 함수 포인터만을 포함하는 구조체 — 이해했으므로, 그 계약을 충족하는 구체적인 구현체(concrete implementations)를 만들 차례입니다.
이전 레슨에서 다루었던 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;
}각 "생성자(constructor)" 함수는 서로 다른 함수가 연결된 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 함수는 로깅 구현과 완전히 분리되어 있습니다. 핵심 로직을 변경하지 않고도 로거를 자유롭게 교체할 수 있습니다. 이것이 바로 인터페이스를 기반으로 프로그래밍하는 것의 힘입니다.
챌린지
쉬움서로 다른 구현이 동일한 인터페이스 계약을 어떻게 충족할 수 있는지 보여주는 INotifier 시스템을 구축해 보겠습니다. 공통 인터페이스를 따르지만 서로 다른 출력을 생성하는 구체적인 알림 도구인 EmailNotifier와 SMSNotifier를 만들 것입니다.
코드는 세 개의 파일로 구성됩니다:
notifier.h: 여기에 인터페이스를 정의합니다.const char*메시지를 매개변수로 받고 아무것도 반환하지 않는NotifyFunc라는 함수 포인터 타입을 생성하세요. 그런 다음 이 타입의notify함수 포인터만 포함하는INotifier구조체를 정의합니다. 또한INotifier를 값으로 반환하는 두 개의 생성자 함수create_email_notifier와create_sms_notifier를 선언하세요.notifier.c: 여기에 구체적인 알림 함수와 생성자를 구현합니다. 다음을 생성하세요:email_notify— 메시지를[EMAIL] message형식으로 출력합니다.sms_notify— 메시지를[SMS] message형식으로 출력합니다.create_email_notifier—email_notify와 연결된INotifier를 반환합니다.create_sms_notifier—sms_notify와 연결된INotifier를 반환합니다.
main.c: 여기서 모든 것을 하나로 합칩니다. 알림 유형과 메시지를 읽고, 생성자 함수를 사용하여 적절한 알림 도구를 생성한 다음, 인터페이스를 통해 알림을 보냅니다.
프로그램은 두 개의 입력을 받습니다: 알림 유형 (이메일은 1, SMS는 2)과 보낼 메시지입니다.
생성자 함수를 사용하여 적절한 알림 도구를 생성한 다음, 인터페이스를 통해 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;
}인터페이스를 사용하는 코드는 구체적인 구현으로부터 분리(decoupled)됩니다:
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;
}직접 해보기
#include <stdio.h>
#include "notifier.h"
int main() {
int type;
char message[256];
// 알림 유형 읽기 (이메일은 1, SMS는 2)
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 생성
// 유형 1에 대해 create_email_notifier() 사용
// 유형 2에 대해 create_sms_notifier() 사용
// TODO: 인터페이스를 통해 notify 함수 호출
// notifier.notify(message);
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.