インターフェースとは?初心者にもわかりやすい具体例で解説

はじめに

インターフェースという言葉を聞くと、初めてプログラミングを学ぶ方には敷居が高いように感じられるかもしれません。 しかし、インターフェースはオブジェクト指向の中でとても基礎的な概念です。 クラスと並んでよく登場するため、どうして必要なのかを理解するとコードの整理や再利用がしやすくなります。 皆さんがインターフェースを学ぶきっかけになればうれしいですね。

ここでは、インターフェースとは何かをわかりやすく解説し、実際の現場でどのように使われているかを具体的に紹介していきます。 初心者の方でも「なるほど、こういう場面で便利なんだ」と思えるよう、実務での活用例も踏まえて説明していきます。

インターフェースとは何か

プログラミングにおいて、インターフェース とは「クラス間のやり取りの約束事」を定義する仕組みです。 たとえば「この機能を使うなら、こういうメソッドを用意してくださいね」という契約のようなイメージです。 具体的には、メソッド名や引数の型、戻り値の型などを定めておくことで、別々に作られたクラス同士でも同じ決まりに従って動作できるようになります。

この仕組みを使うと、クラス同士の依存関係をゆるやかに保つことができるのが特徴です。 後から別のクラスを差し替えたり、違う実装を自由に追加しやすくなります。

オブジェクト指向とインターフェースの関係

オブジェクト指向では、クラスや継承、ポリモーフィズム(多態性)など、複数の概念をうまく組み合わせてコードの再利用を実現します。 その中でもインターフェースは、抽象化 をうまく活かすための仕組みとしてよく使われます。 「このメソッドが呼ばれたら、こう振る舞う」という実装部分はクラス側が持ち、インターフェースは「何をするか」のみを宣言します。

たとえば、以下のような流れを考えてみましょう。

  1. 共通で使いたいメソッド名や機能をインターフェースとしてまとめる
  2. 具体的なクラスは、そのインターフェースを「実装」し、実際のコードを書いていく

これにより、利用側のコードは「インターフェースの形」に合わせて呼び出すだけになります。

実務でのインターフェース活用例

現場で多いのは、ログ出力やデータベース接続のような部分です。 ログ出力の場合、同じ処理でも「ファイルに書き込む実装」や「コンソールに表示する実装」「クラウドに送る実装」などを用意できます。 コード全体を見渡すと、「ログを記録する」という手順自体は変わらず、実際の書き込み方法だけを切り替えることがあるでしょう。

インターフェースを用意しておくと、呼び出し側は「ログを記録するメソッドを呼ぶ」という部分だけ共通化できます。 すると後から「別のログ出力方法を追加したい」と思ったときに、そのインターフェースを実装する新しいクラスを作るだけで済むのです。

インターフェースと抽象クラスの違い

同じようにメソッドの名前や機能を定義する方法として、抽象クラスがあります。 しかし、インターフェースは実装の詳細をまったく含まないため、どのクラスが実装しても構わないという自由度が特徴です。 一方で、抽象クラスは中に共有できる実装を少しだけ書けるので、共通処理をまとめたい場合に適しています。

下の表は、両者の違いをまとめたものです。

項目インターフェース抽象クラス
実装コードを含む含まない(メソッド名や型だけを定義)一部のメソッドに実装を書くことができる
多重実装 / 継承多重実装が可能多重継承は言語によって制限されることが多い
用途全く異なるクラス間の共通仕様を定義部分的に共通する機能をまとめて、サブクラスで利用する

(表1:インターフェースと抽象クラスの違い)

具体的なコード例(Java編)

ここではJavaでのインターフェースを簡単に見てみましょう。 まずはインターフェースを定義します。

public interface MessageSender {
    void sendMessage(String message);
}

MessageSenderというインターフェースには、sendMessage というメソッドが定義されています。 実装クラスはこのメソッドを必ず用意する必要があります。 たとえば下記のように作成してみます。

public class EmailSender implements MessageSender {
    @Override
    public void sendMessage(String message) {
        System.out.println("Email送信: " + message);
    }
}

public class SlackSender implements MessageSender {
    @Override
    public void sendMessage(String message) {
        System.out.println("Slack送信: " + message);
    }
}

ここではEmailSenderやSlackSenderがインターフェースを実装しています。 実装クラス側でsendMessageメソッドの中身を自由に書き分けられるので、同じ呼び出し方でも結果を変えやすくなります。

具体的なコード例(TypeScript編)

TypeScriptでもインターフェースはよく使われます。 オブジェクトの型定義にも使えますが、クラス間の契約として活用することも可能です。

interface PaymentProcessor {
  process(amount: number): void;
}

class CreditCardPayment implements PaymentProcessor {
  process(amount: number): void {
    console.log(`クレジットカードで ${amount} 円を支払いました。`);
  }
}

class CashPayment implements PaymentProcessor {
  process(amount: number): void {
    console.log(`${amount} 円を現金で支払いました。`);
  }
}

function payBill(processor: PaymentProcessor, amount: number) {
  processor.process(amount);
}

payBill(new CreditCardPayment(), 3000);
payBill(new CashPayment(), 2000);

関数payBillは、PaymentProcessor インターフェースを持つオブジェクトならどれでも受け入れます。 こうすることで、支払い手段の追加や切り替えが容易になります。

具体的なコード例(C#編)

C#でも、インターフェースは似たような書き方です。 以下のように定義して実装してみます。

public interface ILogger {
    void Log(string message);
}

public class FileLogger : ILogger {
    public void Log(string message) {
        Console.WriteLine("ファイルにログを書き込み: " + message);
    }
}

public class ConsoleLogger : ILogger {
    public void Log(string message) {
        Console.WriteLine("コンソールにログを表示: " + message);
    }
}

public class Program {
    public static void Main(string[] args) {
        ILogger logger = new FileLogger();
        logger.Log("エラーが発生しました。");

        logger = new ConsoleLogger();
        logger.Log("メッセージを出力しました。");
    }
}

この例では、ILoggerというインターフェースがあり、FileLoggerとConsoleLoggerがそれぞれの方法でログを書き込む処理を行っています。 同じILogger型を扱うコードは、コンソールに出すかファイルに書き込むかを意識せずに利用できます。

インターフェースのメリット

インターフェースを使うメリットにはいろいろな観点があります。 ここでは、代表的なメリットを挙げてみましょう。

  • 実装の差し替えがしやすい
  • クラス同士の依存を減らしてテストしやすくなる
  • 同じ機能を持つが手段が異なる部品を作りやすい

特に実装の差し替えについては、将来的に仕様変更が起きてもコード全体を大きく変えなくて済むので便利です。 そうした拡張性の高さが、開発現場で好まれる理由の一つです。

インターフェースを使う際の注意点

実装の自由度が高い反面、インターフェースを多用しすぎると理解が難しくなることがあります。 全てをインターフェースにしてしまうと、どこで実際の処理をしているのかがわかりにくくなってしまうのです。 また、メソッドの数が増えすぎると、どんな機能を必須にすべきか曖昧になるケースもあるかもしれません。

こういった状態を避けるには、インターフェースが「どんな目的で」作られているのかを明確にすることが大切です。 「この処理を共通化できる」と思えるタイミングで導入するのがいいでしょう。

ネットワークやAPIにおけるインターフェースの考え方

ソフトウェアの世界だけでなく、ネットワーク上でも「APIインターフェース」などと呼ばれる概念があります。 APIの場合は、外部サービスとやりとりをするときの決まりごとを示すものです。 HTTPでのエンドポイントやリクエストとレスポンスのフォーマットを定め、みんなが同じルールで通信できるようにします。

このように、インターフェースは「約束事を決めて、実際の実装を自由に差し替えられる状態にする」という点が共通しています。 プログラミング以外の文脈でも、「接点」「窓口」といった意味を持っているのが面白いところではないでしょうか?

インターフェースを導入すれば必ず可読性が上がるわけではありません。 必要に応じて使いすぎを抑えることも大切です。

テストコードでの利用例

インターフェースを使うとテストがしやすくなります。 たとえばメール送信機能をテストしたいとき、実際にメールを飛ばすと困る場合があります。 しかし、インターフェースを使って実装クラスを差し替えることで、「メールを送らないモック実装」に切り替えられるのです。

これにより、アプリのテストを行うときはモック実装を使い、本番運用時には本物の実装に差し替えるといった運用ができます。 同じ呼び出し方で動くため、他の部分のコードは修正せずに済みます。 これがインターフェースによる柔軟性の恩恵といえるでしょう。

すべてをモックに頼りすぎると、本番の実際の挙動と異なるケースを見落とすことがあります。 テスト方針のバランスが大切ですね。

インターフェースを効果的に使うコツ

インターフェースを使う際に心掛けたいコツは、以下のような点です。

  • 明確な役割を想定してから作る
  • メソッド数は必要最小限に絞る
  • 実装クラスの差し替えパターンをイメージしておく

これらを意識すると、コード全体を整理しやすくなります。 インターフェースを形だけ作るのではなく、クラス同士をどうつなげたいのかを考えると、より効果的に活用できると思います。

まとめ

ここまで、インターフェースとは何かや活用シーン、実際のコード例を紹介してきました。 インターフェースは抽象化を助ける仕組みであり、クラス同士の結びつきをゆるやかにして、実装の差し替えをしやすくしてくれます。 実務においてはログ出力や支払い処理といった場面でよく使われ、メンテナンスや拡張のしやすさにつながっています。

インターフェースを導入するかどうかは、プロジェクトの設計方針や規模感によって変わります。 皆さんも「この機能は将来別の実装に変わるかもしれない」と思ったときに、インターフェースの導入を検討してみるといいでしょう。 「インターフェース」という言葉から少しでも抵抗感がなくなり、プログラミングに対する理解が深まっていけば幸いですね。

Javaをマスターしよう

この記事で学んだJavaの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。