Menu
Coddy logo textTech

インターフェースの実装

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 関数は、ロギングの実装から完全に切り離されています。コアロジックを一切変更することなく、ロガーを自由に入れ替えることができます。これがインターフェースに対してプログラミングすることの力です。

challenge icon

チャレンジ

簡単

異なる実装が同じインターフェースの規約をどのように満たすかを示す INotifier システムを構築しましょう。共通のインターフェースに準拠しながら、異なる出力を生成する具体的な通知機能(EmailNotifierSMSNotifier)を作成します。

コードは以下の3つのファイルに分けて構成します:

  • notifier.h: ここでインターフェースを定義します。const char* のメッセージを受け取り、何も返さない NotifyFunc という関数ポインタ型を作成します。次に、この型の notify 関数ポインタのみを含む INotifier 構造体を定義します。また、INotifier を値で返す2つのコンストラクタ関数 create_email_notifiercreate_sms_notifier を宣言します。
  • notifier.c: ここで具体的な通知関数とコンストラクタを実装します。以下を作成してください:
    • email_notify — メッセージを次の形式で出力します:[EMAIL] message
    • sms_notify — メッセージを次の形式で出力します:[SMS] message
    • create_email_notifieremail_notify に紐付けられた INotifier を返します
    • create_sms_notifiersms_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;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Object Oriented Programmingのすべてのレッスン