インターフェースの概念
CoddyのCジャーニー「Object Oriented Programming」セクションの一部 — レッスン 40/61。
これまで、私たちの構造体はデータと振る舞いを混在させてきました。例えば、名前と関数ポインタを並べるような形です。しかし、もし構造体に関数ポインタだけが含まれていたらどうなるでしょうか?これにより、強力なもの、つまりインターフェースが作成されます。
インターフェースは、それらがどのように動作するかを指定することなく、どのような操作が利用可能であるかを定義します。これは、「このインターフェースを実装するすべてのオブジェクトは、これらの関数を提供しなければならない」という契約です。
typedef void (*LogFunc)(const char* message);
typedef struct {
LogFunc log;
} ILogger;ILogger 構造体は、関数ポインタのみを保持しています。それ自体はデータを持たず、純粋に振る舞いを記述します。「I」という接頭辞は、これがインターフェースであることを示す一般的な慣習です。
ILoggerを受け取るコードは、ログメッセージがどこに送られるかを気にしません。コンソールへの出力、ファイルへの書き込み、あるいはネットワーク経由でのデータ送信など、どこでも構いません。呼び出し側は、単にlogを呼び出せることだけを知っています:
void process(ILogger* logger, const char* msg) {
logger->log(msg); // どのようにログが記録されるかはわかりません
}この分離こそがポリモーフィズムの本質です。process 関数は、どのようなロガーの実装でも動作します。つまり、関数のコードを変更することなく、振る舞いを入れ替えることができます。次のレッスンでは、このインターフェースの契約を満たす具体的な実装を作成します。
チャレンジ
簡単ICalculator インターフェースを構築しましょう。これは関数ポインタのみを含む構造体で、具体的な動作を指定せずに、あらゆる計算機がサポートしなければならない操作を定義します。
コードを整理するために、2つのファイルを作成します:
calculator.h: ここでインターフェースを定義します。2つのint型パラメータを受け取り、int型を返すBinaryOpという名前の関数ポインタ型を作成します。次に、2つの関数ポインタのみを含むICalculator構造体を定義します。それは、operate(BinaryOp型)とdescribe(パラメータなし、戻り値なしの関数ポインタ)です。この構造体は純粋なインターフェースであり、データは保持せず、振る舞いのみを保持します。main.c: ヘッダーをインクルードし、インターフェースを満たす具体的な関数を実装します:add— 2つの整数の和を返しますmultiply— 2つの整数の積を返しますdescribe_add—Additionを出力しますdescribe_multiply—Multiplicationを出力します
ICalculatorインスタンスを作成し、入力に基づいて関数を紐付け、それを使用して計算を実行します。
プログラムは3つの入力を受け取ります:2つの整数と、オペレーションコード(1は加算、2は乗算)です。
オペレーションコードに基づいて、ICalculator に適切な関数のペアを紐付けます。次に、インターフェースを介して describe を呼び出して操作名を出力し、続いて operate を呼び出して結果を計算し出力します。
入力が 5、3、1 の場合の出力例:
Addition
8入力が 4、7、2 の場合の出力例:
Multiplication
28重要なポイントは、ICalculator 構造体が契約を定義しているということです。それを使用するコードは、どの特定の関数が紐付けられているかを知らなくても、describe と operate を呼び出せることを知っています。ヘッダーファイルではインクルードガードを使用することを忘れないでください。
チートシート
インターフェースとは、関数ポインタのみを含む構造体であり、操作がどのように機能するかを指定せずに、どのような操作が利用可能かを定義するものです。
インターフェースの定義:
typedef void (*LogFunc)(const char* message);
typedef struct {
LogFunc log;
} ILogger;「I」という接頭辞は、これがインターフェースであることを示す一般的な慣習です。この構造体にはデータは含まれず、純粋に振る舞いを記述します。
インターフェースの使用:
void process(ILogger* logger, const char* msg) {
logger->log(msg); // どのようにログが記録されるかは関知しません
}この分離により、ポリモーフィズム(多態性)が可能になります。関数はインターフェースのあらゆる実装で動作するため、関数のコードを変更することなく振る舞いを入れ替えることができます。
自分で試してみよう
#include <stdio.h>
#include "calculator.h"
// TODO: add関数を実装する
// 2つの整数の和を返す
// TODO: multiply関数を実装する
// 2つの整数の積を返す
// TODO: describe_add関数を実装する
// "Addition"と表示する
// TODO: describe_multiply関数を実装する
// "Multiplication"と表示する
int main() {
int num1, num2, op_code;
scanf("%d", &num1);
scanf("%d", &num2);
scanf("%d", &op_code);
// TODO: ICalculatorのインスタンスを作成する
// TODO: op_code(1は加算、2は乗算)に基づいて、
// 適切な関数を電卓に紐付ける
// TODO: インターフェースを通じてdescribeを呼び出す
// TODO: インターフェースを通じてoperateを呼び出し、結果を表示する
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。