インターフェースの実装
CoddyのCジャーニー「Object Oriented Programming」セクションの一部 — レッスン 41/61。
インターフェースとは何か — 関数ポインタのみを含む構造体 — を理解したところで、そのコントラクトを満たす具体的な実装を作成する時が来ました。
前のレッスンの 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;
}各「コンストラクタ」関数は、異なる関数が組み込まれた 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)を作成します。
コードは以下の3つのファイルに分けて構成します:
notifier.h: ここでインターフェースを定義します。const char*のメッセージを受け取り、何も返さないNotifyFuncという関数ポインタ型を作成します。次に、この型のnotify関数ポインタのみを含むINotifier構造体を定義します。また、INotifierを値で返す2つのコンストラクタ関数create_email_notifierとcreate_sms_notifierを宣言します。notifier.c: ここで具体的な通知関数とコンストラクタを実装します。以下を作成してください:email_notify— メッセージを次の形式で出力します:[EMAIL] messagesms_notify— メッセージを次の形式で出力します:[SMS] messagecreate_email_notifier—email_notifyに紐付けられたINotifierを返しますcreate_sms_notifier—sms_notifyに紐付けられたINotifierを返します
main.c: ここですべてをまとめます。通知タイプとメッセージを読み取り、コンストラクタ関数を使用して適切な通知機能を作成し、インターフェースを通じて通知を送信します。
プログラムは2つの入力を受け取ります:通知タイプ(1はメール、2はSMS)と、送信するメッセージです。
コンストラクタ関数を使用して適切な通知機能を作成し、インターフェースを介して notify を呼び出してメッセージを表示してください。
タイプが 1 でメッセージが Meeting at 3pm の場合の出力例:
[EMAIL] Meeting at 3pmタイプが 2 でメッセージが Your code shipped の場合の出力例:
[SMS] Your code shippedこのパターンの強力な点は、main.c が各通知機能の内部動作を知る必要がないことです。単にインターフェースの notify 関数を呼び出すだけで、正しい実装が実行されます。ヘッダーファイルにはインクルードガードを使用することを忘れないでください。
チートシート
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;
}インターフェースを使用するコードは、具体的な実装から切り離されます:
void do_work(ILogger* logger) {
logger->log("Starting work...");
}
int main() {
ILogger console = create_console_logger();
ILogger file = create_file_logger();
do_work(&console); // console_logを使用
do_work(&file); // file_logを使用
return 0;
}自分で試してみよう
#include <stdio.h>
#include "notifier.h"
int main() {
int type;
char message[256];
// 通知タイプを読み込む (1 は email、2 は SMS)
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: type に基づいて適切な notifier を作成する
// type 1 の場合は create_email_notifier() を使用する
// type 2 の場合は create_sms_notifier() を使用する
// TODO: インターフェースを介して notify 関数を呼び出す
// notifier.notify(message);
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。