プログラミングを学んでいると、「インターフェース」と「クラス」の違いがわからなくなることはないでしょうか。さらに「抽象クラス」も登場すると、どれをどう使い分ければいいのか迷ってしまう方も多いでしょう。
本記事では、インターフェースとクラスの違い・抽象クラスとの比較・オブジェクト指向での使い分けをわかりやすく解説していきます。
インターフェースとクラスの最大の違いは「実装を持つかどうか」
それではまず、インターフェースとクラスの根本的な違いについて解説していきます。
クラスはデータ(フィールド)と処理(メソッド)の実装をまとめた設計図です。一方、インターフェースは「何ができるか」という仕様だけを定義したもので、原則として処理の実装を持ちません。
クラスがインターフェースを「実装(implement)」することで、インターフェースで定義されたメソッドを必ず持つことが保証されます。これがオブジェクト指向プログラミングにおける「契約」の考え方です。
インターフェースは「何をするか(What)」を定義し、クラスは「どのようにするか(How)」を実装します。この役割分担を意識することで、インターフェースとクラスの使い分けが自然と理解できるようになります。
クラスの基本的な特徴
クラスはオブジェクト指向プログラミングの基本単位です。フィールド(変数)・コンストラクタ・メソッドを持ち、インスタンスを生成して使います。
クラスは他のクラスを「継承(extends)」することで、親クラスの機能を引き継ぎながら新たな機能を追加できます。ただし、多くの言語では1つのクラスしか継承できない「単一継承」の制約があります。
インターフェースの基本的な特徴
インターフェースはメソッドのシグネチャ(名前・引数・戻り値の型)のみを定義し、具体的な処理は実装クラスに委ねます。
クラスと異なり、1つのクラスが複数のインターフェースを実装できる「多重実装」が可能です。これにより、単一継承の制約を補いながら柔軟な設計が実現できます。
| 比較項目 | クラス | インターフェース |
|---|---|---|
| 実装の有無 | あり | 原則なし |
| インスタンス生成 | 可能 | 不可 |
| 継承・実装数 | 単一継承 | 複数実装可能 |
| フィールド | 持てる | 原則持てない |
| 主な用途 | 処理の実装・再利用 | 仕様の定義・型の統一 |
Javaでのインターフェースとクラスの書き方
Javaを例にインターフェースとクラスの基本的な書き方を確認しましょう。
// インターフェースの定義
interface Grindable {
void grind(); // メソッドのシグネチャのみ定義
}
// クラスがインターフェースを実装
class CoffeeBeanGrinder implements Grindable {
@Override
public void grind() {
System.out.println(“コーヒー豆を挽いています”);
}
}
// 実行
public class Main {
public static void main(String[] args) {
Grindable grinder = new CoffeeBeanGrinder();
grinder.grind();
}
}
# 出力結果:コーヒー豆を挽いています
抽象クラスとインターフェースの違い
続いては、抽象クラスとインターフェースの違いと使い分けについて確認していきます。
抽象クラスはインターフェースとクラスの中間的な存在です。「抽象メソッド(実装なし)」と「具体的なメソッド(実装あり)」の両方を持てる点が特徴です。
抽象クラスの特徴と使い方
抽象クラスはabstractキーワードで定義し、インスタンスを直接生成することはできません。サブクラスに継承させて抽象メソッドをオーバーライドして使います。
// 抽象クラスの定義
abstract class Vehicle {
String name;
```
Vehicle(String name) {
this.name = name;
}
// 抽象メソッド(サブクラスで実装必須)
abstract void move();
// 具体的なメソッド(共通処理)
void showName() {
System.out.println("乗り物名:" + name);
}
```
}
// サブクラスで抽象メソッドを実装
class Bicycle extends Vehicle {
Bicycle() {
super(“自転車”);
}
```
@Override
public void move() {
System.out.println("ペダルを漕いで進む");
}
```
}
public class Main {
public static void main(String[] args) {
Vehicle v = new Bicycle();
v.showName();
v.move();
}
}
# 出力結果:乗り物名:自転車
# 出力結果:ペダルを漕いで進む
抽象クラスとインターフェースの使い分け
抽象クラスとインターフェースはどちらも「実装を強制する仕組み」ですが、使い分けのポイントは以下のとおりです。
| 比較項目 | 抽象クラス | インターフェース |
|---|---|---|
| 実装の共有 | 可能(共通処理を持てる) | 原則不可 |
| 多重継承・実装 | 単一継承のみ | 複数実装可能 |
| フィールド | 持てる | 原則持てない |
| 向いているケース | 共通の処理・状態を持たせたいとき | 型の統一・多重実装が必要なとき |
「共通の処理や状態を持たせたいなら抽象クラス、型の統一や多重実装が必要ならインターフェース」という基準で選ぶとスムーズです。
UMLクラス図での表現方法
UMLクラス図では、インターフェースは「<
抽象クラスはクラス名をイタリック体で表記するか、「{abstract}」という制約を付けて表現します。UMLでの表記を理解しておくことで、設計書の読み書きがスムーズになるでしょう。
オブジェクト指向設計でのインターフェースの活用
続いては、実際の設計においてインターフェースをどのように活用するかを確認していきます。
依存性の逆転原則とインターフェース
オブジェクト指向設計の原則のひとつに「依存性の逆転原則(DIP)」があります。これは具体的な実装クラスではなく、インターフェースに依存するように設計する考え方です。
インターフェースを介することで、実装クラスを差し替えてもコードの変更が最小限で済みます。テストの際にモック(擬似オブジェクト)を使いやすくなるという利点もあります。
複数インターフェースの実装例
Javaでは1つのクラスが複数のインターフェースを実装できます。以下はその例です。
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
// 複数のインターフェースを実装
class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println(“アヒルが飛んでいます”);
}
```
@Override
public void swim() {
System.out.println("アヒルが泳いでいます");
}
```
}
public class Main {
public static void main(String[] args) {
Duck duck = new Duck();
duck.fly();
duck.swim();
}
}
# 出力結果:アヒルが飛んでいます
# 出力結果:アヒルが泳いでいます
インターフェースを使うメリットまとめ
インターフェースを活用することで得られる主なメリットは以下のとおりです。
まず、実装を強制することでチーム開発における仕様の統一が図れます。次に、複数のインターフェースを実装できるため設計の柔軟性が高まります。さらに、実装クラスを差し替えやすくなることで、テストのしやすさや保守性の向上にもつながります。
まとめ
本記事では、インターフェースとクラスの違い・抽象クラスとの比較・オブジェクト指向設計での活用方法について解説しました。
クラスは処理の実装を持つ設計図であり、インターフェースは仕様のみを定義したものです。抽象クラスはその中間で、共通処理と抽象メソッドの両方を持てます。使い分けの基準は「共通の処理を持たせたいなら抽象クラス、型の統一や多重実装が必要ならインターフェース」です。
インターフェースを適切に活用することで、柔軟性・保守性・テストのしやすさが向上します。オブジェクト指向設計の理解を深めながら、場面に応じた使い分けを身につけていきましょう。