💭

インターフェースってなに?

2025/02/15に公開

はじめに

実務で 「クラスに新しいメソッドを追加するときは、インターフェースも追加して」と引き継ぎがあったので、ルールとして守っていたものの「なぜインターフェースが必要なのか?」という疑問がずっとあった。
そこで改めて調べてみると、ただのルールではなく、設計を柔軟にする大事な役割があることが分かった。

インターフェース とは

「インターフェース」という言葉は「接点」や「境界面」という意味を持っている。
例えば「ユーザーインターフェース (UI)」という言葉は聞き慣れているかもしれない。
UIとは「ユーザーがコンピュータを操作するための接点」。
キーボードやマウスを使えば、コンピュータの内部を知らなくても操作できる。

プログラミングにおけるインターフェースは、「クラス同士の共通の接点」を定義する。
どんなメソッドを持つべきか、どんなデータをやりとりできるかを決める役割を持っている。

主なルール

  1. インターフェースを継承したクラスは、定義されたメソッドを必ず実装する
  2. インターフェースを型として利用すると、インターフェースに定義されたメソッドのみアクセス可能
interface InterfaceA {
    method1(): void; // InterfaceA を実装するクラスは method1 を定義する必要がある
}

class ClassA implements InterfaceA {
    method1(): void {
        console.log("ClassA - method1()");
    }
    method2(): void {
        console.log("ClassA - method2()");
    }
}

let obj: InterfaceA = new ClassA(); // クラスA のインスタンスを InterfaceA 型で受け取る
obj.method1(); // OK (InterfaceA に定義されているメソッド)
obj.method2(); // エラー (InterfaceA に定義されていないため、アクセス不可)

https://typescriptbook.jp/reference/object-oriented/interface

インターフェースのメリット:柔軟な設計と拡張性

特定のクラスに依存しない柔軟な設計が可能になる

インターフェースを引数にすることでInterfaceA に従うクラスであれば何でも受け取れるため、ClassB や ClassC でも対応可能になる。

const someFunction = (obj: InterfaceA) => {
    obj.method1();
};

someFunction(new ClassA()); // OK

ユーザーインターフェースの入力で考えてみる

// 入力デバイスのインターフェース
interface IInputDevice {
    input(): string;
}

// キーボード入力クラス
class KeyboardInput implements IInputDevice {
    input(): string {
        return "キーボードで入力されました";
    }
}

// マウス入力クラス
class MouseInput implements IInputDevice {
    input(): string {
        return "マウスクリックで入力されました";
    }
}

// タッチスクリーン入力クラス
class TouchScreenInput implements IInputDevice {
    input(): string {
        return "タッチスクリーンで入力されました";
    }
}

// 実行例
function handleInput(device: IInputDevice) {
    console.log(device.input());
}

// キーボードで入力
handleInput(new KeyboardInput());

// マウスで入力
handleInput(new MouseInput());

// タッチスクリーンで入力
handleInput(new TouchScreenInput());

1.新しい入力デバイスを追加するのが簡単

たとえば、新しく 音声入力 (VoiceInput) を追加する場合でも、既存の handleInput のロジックは変更せずに済むので、すでにある KeyboardInput や MouseInput に影響を与えないで追加することができる。

// 新しい入力デバイス(音声入力)を追加
class VoiceInput implements IInputDevice {
    input(): string {
        return "音声で入力されました";
    }
}

// 既存の処理を変更せずに新デバイスを利用
handleInput(new VoiceInput());

2.既存のデバイスの仕様変更が容易になる

マウスのダブルクリックを入力として処理するというような仕様変更があった場合でも、MouseInput のクラス内だけを修正すれば済む。

変更前
class MouseInput implements IInputDevice {
    input(): string {
        return "マウスクリックで入力されました";
    }
}
変更後(ダブルクリック対応)
class MouseInput implements IInputDevice {
    input(): string {
        return "マウスのダブルクリックで入力されました";
    }
}

3.テストがしやすい

テスト用のモックに置き換えることができる。

// テスト用の入力デバイス
class MockInput implements IInputDevice {
    input(): string {
        return "テスト用の入力";
    }
}

// handleInput のテスト
handleInput(new MockInput());

まとめ

  • インターフェースを使った設計は変更や拡張がしやすくなる
  • オープン・クローズドの原則 に従い、追加時に既存コードを変更しなくて済む
  • インターフェースを見るだけでクラスの役割が分かるため、コードが読みやすくなる

Discussion