🕛 2024.5.19 03:57

【実践】Hands-On で理解するオブジェクト指向プログラミング(OOP) インターフェースと抽象クラス

  • クラスの使い方はわかるが、インターフェースと抽象クラスの違いがわからない。
  • どのような場合にインターフェースや抽象クラスを使えばよいのか判断できない。
  • インターフェースや抽象クラスを具体的にどのように実装すればよいのかわからない。

プログラマー歴20年の管理人が、これらの悩みを解決できるように、手を動かしながらOOPのインターフェースと抽象クラスを学べるように記事を書きました!

この記事を読むことで、インターフェースと抽象クラスの違いを明確に理解し、それぞれをどのように実装し、どのような場合に使えばよいのかがわかります。これにより、コードの設計と実装がスムーズに進みます。

  • インターフェースと抽象クラスの違いを理解:それぞれの役割と使い分けを理解します。
  • インターフェースの実装方法:インターフェースを定義し、実装するクラスを作成する方法を学びます。
  • 抽象クラスの実装方法:抽象クラスを定義し、具体クラスで抽象メソッドを実装する方法を学びます。

この記事を読み終えた後、このコードを理解し、実践的なスキルが身についているはずです。

<?php

// インターフェースの定義
interface AnimalInterface {
    public function makeSound();
}

// インターフェースの実装クラス
class Dog implements AnimalInterface {
    public function makeSound() {
        return "Woof!";
    }
}

class Cat implements AnimalInterface {
    public function makeSound() {
        return "Meow!";
    }
}

// ポリモーフィズムを示す関数
function describeAnimal(AnimalInterface $animal) {
    echo $animal->makeSound() . "\n";
}

// インターフェースのテスト
$dog = new Dog();
$cat = new Cat();

describeAnimal($dog); // 出力: Woof!
describeAnimal($cat); // 出力: Meow!

// 抽象クラスの定義
abstract class Animal {
    protected $name;

    public function __construct($name) {
        $this->name = $name;
    }

    abstract public function makeSound();

    public function describe() {
        return $this->name . " says " . $this->makeSound();
    }
}

// 抽象クラスの実装クラス
class AbstractDog extends Animal {
    public function makeSound() {
        return "Woof!";
    }
}

class AbstractCat extends Animal {
    public function makeSound() {
        return "Meow!";
    }
}

// 抽象クラスのテスト
$abstractDog = new AbstractDog("Buddy");
$abstractCat = new AbstractCat("Whiskers");

echo $abstractDog->describe() . "\n"; // 出力: Buddy says Woof!
echo $abstractCat->describe() . "\n"; // 出力: Whiskers says Meow!

?>

これから、インターフェースと抽象クラスの基本概念、実装方法、そしてそれぞれの使い分けについて具体例を通じて解説します。

インターフェースとは?

次のコードを書いてみましょう。interface キーワードを使ってインターフェースを定義します。

AnimalInterface という名前のインターフェースを作成し、makeSound メソッドを宣言します。

<?php
// インターフェースの定義
interface AnimalInterface {
    public function makeSound();
}
?>

インターフェースが定義できました。

インターフェースは、クラスがどんなメソッドを持つべきかを決めるためのテンプレートです。具体的に何をするかは書かれておらず、「このメソッドを持ちなさい」と決めるだけです。

インターフェースを使うのか

コードの一貫性:

  • インターフェースを使うことで、異なるクラスが同じメソッドを持つことを保証します。

柔軟で拡張可能:

  • 新しいクラスを追加する際に既存のコードを変更せずに済みます。これにより、コードが柔軟で拡張可能になります。

依存関係の低減:

  • クラス間の依存関係が減り、コードの保守性が向上します。

多様な実装を許容:

  • 異なるクラスのオブジェクトを同じ方法で扱うことができ、コードの再利用性が向上します。

続きのコードを書きましょう。インタフェースを実装していきます。

implements キーワードを使って、クラスがインターフェースを実装していることを示します。makeSound メソッドをそれぞれのクラスで具体的に実装します。

<?php
// インターフェースの定義
interface AnimalInterface {
    public function makeSound();
}

// Dog クラスが AnimalInterface を実装
class Dog implements AnimalInterface {
    public function makeSound() {
        return "Woof!";
    }
}

// Cat クラスが AnimalInterface を実装
class Cat implements AnimalInterface {
    public function makeSound() {
        return "Meow!";
    }
}
?>

Dog クラスと Cat クラスは、それぞれ AnimalInterface を実装しています。

describeAnimal 関数は AnimalInterface 型の引数を受け取ります。Dog クラスと Cat クラスのオブジェクトを作成し、describeAnimal 関数に渡します。

<?php
// インターフェースの定義
interface AnimalInterface {
    public function makeSound();
}

// Dog クラスが AnimalInterface を実装
class Dog implements AnimalInterface {
    public function makeSound() {
        return "Woof!";
    }
}

// Cat クラスが AnimalInterface を実装
class Cat implements AnimalInterface {
    public function makeSound() {
        return "Meow!";
    }
}
// describeAnimal 関数
function describeAnimal(AnimalInterface $animal) {
    echo $animal->makeSound() . "\n";
}

// オブジェクトの作成
$dog = new Dog();
$cat = new Cat();

// describeAnimal 関数を使って動作を確認
describeAnimal($dog); // 出力: Woof!
describeAnimal($cat); // 出力: Meow!
?>
$ php lesson3.php
Woof!
Meow!

インターフェースを使うと、異なるクラスのオブジェクトを同じ方法で扱えます。コードの柔軟性と再利用性が向上します。

継承を学習した際、使用したクラスを具体クラスします。具体クラスとインタフェースをなにが違うのでしょうか。

インターフェースと具体クラスの違い

特徴インターフェース(Interface)具体クラス(Concrete Class)
定義内容メソッドのテンプレート(契約)プロパティやメソッドの具体的な実装
実装具体的な実装を持たないすべてのメソッドが実装されている
インスタンス化できないできる
使用目的クラスの契約を定義し、一貫性を持たせる直接オブジェクトを作成するため
抽象メソッドすべて抽象メソッド含まない
継承関係クラスは複数のインターフェースを実装できる単一の親クラスからのみ継承できる
アクセス修飾子すべてのメソッドはデフォルトで public任意のアクセス修飾子を使用できる
interface AnimalInterface {}class Dog {}
共通の機能の提供持たない必要に応じて実装
設計の柔軟性異なるクラス間で同じメソッドを保証する具体的な動作を実装
再利用性異なるクラスに共通のメソッドを定義できる具体的な動作を他のクラスで再利用
使用例異なるクラス間で同じメソッドを持つことを保証する共通のプロパティやメソッドを継承するため

抽象クラスはどんな時に使うのか?

インターフェースは、クラスが実装すべきメソッドを定義し、異なるクラスに共通の決めるために使用します。

抽象クラスとは?

まず、抽象クラスを定義します。次のコードを書いてみましょう。

abstract キーワードを使って抽象クラスを定義します。

Animal 抽象クラスには、コンストラクタと共通メソッド describe を書きます。

makeSound メソッドは抽象メソッドとして定義します。具体的な実装は持たないので書きません。

<?php
// 抽象クラスの定義
abstract class Animal {
    public $name;

    public function __construct($name) {
        $this->name = $name;
    }

    // 抽象メソッドの定義
    abstract public function makeSound();

    // 共通メソッドの定義
    public function describe() {
        return $this->name . " says " . $this->makeSound();
    }
}
?>

抽象クラスは、共通の機能や特性を持つクラスを作成するためのテンプレートです。具体的なクラスに共通する部分は抽象クラスで定義し、具体的な実装は子クラスで行います。

抽象クラスは abstract キーワードを使って定義し、抽象メソッドを含むことができます。抽象メソッドは実装を持たず、サブクラスで必ず実装されるべきメソッドです。

次に、抽象クラスを継承する具体的なクラスを作成します。

Dog クラスと Cat クラスは、Animal 抽象クラスを継承します。

各クラスは、抽象メソッド makeSound を具体的に実装します。

<?php
// 抽象クラスの定義
abstract class Animal {
    public $name;

    public function __construct($name) {
        $this->name = $name;
    }

    // 抽象メソッドの定義
    abstract public function makeSound();

    // 共通メソッドの定義
    public function describe() {
        return $this->name . " says " . $this->makeSound();
    }
}

// 抽象クラスを継承するクラス
class Dog extends Animal {
    // 抽象メソッドの実装
    public function makeSound() {
        return "Woof!";
    }
}

class Cat extends Animal {
    // 抽象メソッドの実装
    public function makeSound() {
        return "Meow!";
    }
}
?>

子クラスは抽象クラスを継承し、抽象メソッドを具体的に実装する必要があります。

最後に、抽象クラスと具体的なクラスを使ってオブジェクトを作成し、メソッドを呼び出して動作を確認します。

<?php
// 抽象クラスの定義
abstract class Animal {
    public $name;

    public function __construct($name) {
        $this->name = $name;
    }

    // 抽象メソッドの定義
    abstract public function makeSound();

    // 共通メソッドの定義
    public function describe() {
        return $this->name . " says " . $this->makeSound();
    }
}

// 抽象クラスを継承するクラス
class Dog extends Animal {
    // 抽象メソッドの実装
    public function makeSound() {
        return "Woof!";
    }
}

class Cat extends Animal {
    // 抽象メソッドの実装
    public function makeSound() {
        return "Meow!";
    }
}

// 抽象クラスのオブジェクトの作成と利用
$dog = new Dog("Buddy");
$cat = new Cat("Whiskers");

echo $dog->describe() . "\n"; // 出力: Buddy says Woof!
echo $cat->describe() . "\n"; // 出力: Whiskers says Meow!
?>
$ php lesson4.php
Buddy says Woof!
Whiskers says Meow!

抽象クラスはどんな時に使うのか?

子クラスに特定のメソッドの実装を必須にさせて具体的な実装内容は子クラスに任せたい場合に使えます。具体クラスでは親クラスで定義したメソッドを子クラスで使わなくても(オーバーライド)エラーになりません。

インターフェースとクラス

インターフェースとクラスを実際に動物と車のクラスを実装しながらイメージしていきたいと思います。

インターフェースの定義

インタフェースを使用して動物と車に共通の「走る」というメソッドを設計します。

Runnable という名前のインターフェースを作成します。このインターフェースは「走る」という動作を定義します。

interface Runnable {
    public function run();
}

インターフェースは「このメソッドを持ちなさい」という約束(契約)を定義します。具体的な実装は含まず、クラスに対して「このメソッドを必ず実装しなさい」と指示するだけです。

抽象クラスの定義

次に、動物と車の抽象クラスを定義します。それぞれの抽象クラスは Runnable インターフェースを実装し、共通のプロパティを持ちます。

抽象クラスは共通のプロパティやメソッドを持ち、部分的に実装されたクラスです。

インスタンス化(具体的なオブジェクトを作成すること)はできません。

子クラスはこの抽象クラスを継承し、抽象メソッドを実装しなければなりません。

動物の抽象クラス

abstract class Animal implements Runnable {
    protected $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }

    abstract public function run();
}

車の抽象クラス

abstract class Vehicle implements Runnable {
    protected $model;

    public function __construct($model) {
        $this->model = $model;
    }

    public function getModel() {
        return $this->model;
    }

    abstract public function run();
}

具体クラスの実装

次に、動物と車の具体的なクラスを作成し、それぞれの「走る」動作を実装します。

具体クラスは抽象クラスを継承し、抽象メソッドを実装するクラスです。

具体的な動作を定義できます。インスタンス化してオブジェクトを作成することができます。

具体的な動物クラス

class Dog extends Animal {
    public function run() {
        echo $this->name . " is running on four legs.\n";
    }
}

class Cheetah extends Animal {
    public function run() {
        echo $this->name . " is running very fast!\n";
    }
}

具体的な車クラス

class Car extends Vehicle {
    public function run() {
        echo $this->model . " is driving on the road.\n";
    }
}

class Motorcycle extends Vehicle {
    public function run() {
        echo $this->model . " is speeding on two wheels.\n";
    }
}

使用例

最後に、具体的なクラスを使ってオブジェクトを作成し、メソッドを呼び出します。

$dog = new Dog("Buddy");
$cheetah = new Cheetah("Chester");
$car = new Car("Toyota");
$motorcycle = new Motorcycle("Harley-Davidson");

$dog->run(); // 出力: Buddy is running on four legs.
$cheetah->run(); // 出力: Chester is running very fast!
$car->run(); // 出力: Toyota is driving on the road.
$motorcycle->run(); // 出力: Harley-Davidson is speeding on two wheels.
  • インターフェース(Runnable): 走る(run)という動作の契約を定義します。
  • 抽象クラス(Animal, Vehicle): 共通のプロパティやメソッドを持ちつつ、走る(run)動作を具体的に実装することを子クラスに強制します。
  • 具体クラス(Dog, Cheetah, Car, Motorcycle): 抽象クラスを継承し、具体的な走る(run)動作を実装します。

まとめ

インターフェース

  • 共通のメソッドを持たせる約束
  • 実装を持たない

抽象クラス

  • 共通のプロパティと一部の実装を提供
  • 具体的に実装することを子クラスに必須にさせる
  • インスタンス化不可

具体クラス

  • 抽象クラスを継承し、具体的な実装を提供
  • インスタンス化可

異なるクラスが共通のメソッドを持ちつつ、それぞれの特徴に応じた具体的な動作を実装でき、コードの柔軟性と再利用性が向上します。