79
81

More than 1 year has passed since last update.

ポケモンのクラスを作ってPHPオブジェクト指向の3大要素を理解する

Posted at

はじめに

こんにちは。masakichiです。

PHPのオブジェクト指向には3大要素というものがあります。
「カプセル化」「継承」「ポリモーフィズム」の3つだそうです。

知らない人からすると、なんのこっちゃだと思います。
これらを理解するには、PHPのクラスから学んでいくといいと考えています。

ということで、まずはPHPのクラスについて理解するために、ポケモンを通して実装例を書いていこうと思います。

ポケモンには様々な種類がいます。
一番最初は151匹だったのに、いまでは900種類以上いるそうです。

とても覚えられたものじゃありません。

さて、いきなりですが、ここであなたはポケモンの開発者になったとします。

900種類以上のポケモンを表現するために、プログラミングコードを記述しなくてはいけません。
そんな時、1匹ずつ別々のプログラミングコードを記述することで実現は可能かもしれません。

しかし、そのやり方だと、もしまた新しいポケモンが発売された時、同じように新種のポケモンの数だけコードを書き足さなくてはいけなくなります。

そんな時に役立つのがクラスという考え方です。

クラスでは各々のポケモンが共通して持つ情報をまとめて定義しておくことができるのです。
クラスに共通の情報をまとめておけば、新しいポケモンが出たとしても、そのコードを使いまわせるので大分労力が削減されるはずです。

ではクラスについて少しづつ見ていきましょう。

クラスが共通して持つ情報とは?

クラスはポケモンが共通して持つ情報をまとめて定義しておくことができると書きました。

共通して持つ情報は2つに分けることができます。

プロパティとメソッドです。

プロパティとは、ポケモンの特徴・特性のことで、例えば名前だったり、タイプだったり、体重だったりします。

次にメソッドとは、ポケモンができることで、例えば攻撃したり、進化したり、鳴き声を出したりなどです。

ポケモンのクラスを作った場合を表にすると下記のようになります。

Class Pokemon
プロパティ 名前、タイプ、体重
メソッド 攻撃する、進化する、鳴き声をだす

また、ここではまだ理解できなくても大丈夫ですが、このクラスを使ってヒトカゲを作るとすると下記のようになります。

Class Pokemon
プロパティ ヒトカゲ、ほのおタイプ、8.5kg
メソッド ひのこ、リザードへ進化、かげかげー

こんな感じで、クラスにはポケモンが共通して持つ情報をあらかじめ定義しておくことができます。

ここまでの内容をコードにしてみる

ここまでの内容をコードにしてみます。
上記の表ではプロパティやメソッドが複数ありましたが、コード簡略化のためにプロパティは名前だけ。メソッドは進化だけにします。

Pokemon.php
<?php
class Pokemon
{
  //名前プロパティ
  private $name;

  //進化メソッド
  public function evolve($evolvedName)
  {
    echo "おめでとう!".$this->name."は".$evolvedName."に進化した!";
  }
}

クラスの定義方法

まずクラスはclass クラス名で定義し、{}で囲むと作ることができます。
このクラスの中に、プロパティやメソッドを記述していきます。

Pokemon.php
<?php
class Pokemon {}

プロパティ

以下の部分です。

Pokemon.php
//名前プロパティ
private $name;

プロパティといっても実態はただの変数です。あとから、この変数に値を格納することで、名前を表現することができるようになります。

またプロパティの前にprivateという記述があります。これをつけると、クラス内でしか操作・呼び出しすることができなくなります。

ここでは、「へー」くらいで理解しておけば大丈夫です。
後のカプセル化という概念まで学ぶと理解できるようになります。

メソッド

以下の部分です。

Pokemon.php
//進化メソッド
public function evolve($evolvedName)
{
  echo "おめでとう!".$this->name."は".$evolvedName."に進化した!";
}

メソッドといっても、実態はただ関数を作っているだけです。
例では、メソッドを呼び出すと「おめでとう!〇〇は〇〇に進化した!」というテキストがエコーされるようになっています。

なので、先程の$nameプロパティに「ヒトカゲ」が入っていたとします。そして、evolveメソッドの引数に「リザード」を渡して呼び出してあげれば「おめでとう!ヒトカゲはリザードに進化した!」と表示されるわけです。

またメソッドの前にpublicという記述があります。これをつけるとクラスの外からでも関数を呼び出して使うことができるようになります。

それから$nameの前に$thisとありますが、これは自身のクラスを表します。

インスタンス〜クラスからヒトカゲを作ってみる〜

上記までで、Pokemonクラスの雛形をつくることができました。
では実際に、Pokemonクラスからヒトカゲのオブジェクトを作ってみることにします。

ちなみに、Pokemonというクラスからつくったもの(オブジェクト。この場合はヒトカゲ。)をインスタンスといいます。

インスタンスをつくる

インスタンスをつくるには下記のように記述します。

index.php
require_once('./Pokemon.php');

$hitokage = new Pokemon('ヒトカゲ');

まず、作成したPokemonクラスを使うために、phpのrequire_once()でファイルを読み込んでおきます。
そして、クラスからインスタンスを作るにはnew クラス名()とすれば作れます。

作ったインスタンスは$hitokage変数に格納しておきます。

さて、ここでひとつ疑問点が残っています。
クラスに「ヒトカゲ」という引数を渡しています。

この引数をクラス内で受け取るためには、コンストラクタというものを定義する必要があります。

コンストラクタ 〜"ヒトカゲ"を$nameプロパティに格納する〜

コンストラクタとは、インスタンスが生成されたときに一番最初に実行される関数です。

クラス内で、下記のように記述するとコンストラクタ関数になります。

Pokemon.php
function __construct(){}

コンストラクタ関数には一番最初に実行されるだけでなく、インスタンスから渡される値を引数として受け取ることができるという特徴もあります。

つまり、先程のnew Pokemo('ヒトカゲ')の「ヒトカゲ」という名前を以下のように記述すれば引数として受け取ることができるわけです。

Pokemon.php
  function __construct($name)
  {
    echo $name;
    // >> ヒトカゲ
  }

では、せっかく名前を受け取ったので、$nameプロパティに値を格納しておきましょう。
以下のようにします。

Pokemon.php
  function __construct($name)
  {
    $this->name = $name;
  }

$thisは自身のクラスを表していました。自身のクラスの$nameプロパティにコンストラクタ関数が受け取った「ヒトカゲ」を格納することで、無事$nameプロパティの値は「ヒトカゲ」になります。

インスタンスから進化メソッドにアクセスして、ヒトカゲを進化させる

ここまでで、Pokemonクラスを作り、そこからインスタンスを生成してヒトカゲをつくることができました。

次に、クラス内で定義した進化メソッドにアクセスして、ヒトカゲを進化させてみます。

なお、進化メソッドは以下の関数でした。

Pokemon.php
//進化メソッド
public function evolve($evolvedName)
{
  echo "おめでとう!".$this->name."は".$evolvedName."に進化した!";
}

インスタンスからメソッドにアクセスするには以下のようにします。

index.php
<?php
require_once('./pokemon.php');

$hitokage = new Pokemon('ヒトカゲ');
echo $hitokage->evolve("リザード");//追記

この結果、以下のように出力されます。

おめでとう!ヒトカゲはリザードに進化した!

無事に進化できました。おめでとうございます。

オブジェクトの3大要素を駆使して、ピカチュウもつくる

さて、ようやく本題です。(長かった…)

冒頭で述べたように、オブジェクト指向には3大要素と呼ばれる概念があります。
「カプセル化」「継承」「ポリモーフィズム」です。

なので、3大要素の理解をすることで、オブジェクト指向の入り口に立つことくらいはできるだろうと個人的には考えてます。

カプセル化

まず、最初に「カプセル化」についてです。

カプセルと聞くと、ガチャガチャとかで出てくるものとかを思い浮かべると思いますが、コンピュータプログラミングで用いられる場合は「内部隠蔽」を表します。

つまり、オブジェクトの中身を隠すことなのですが、そのメリットのひとつとして、オブジェクトのプロパティとかメソッドの内部要素に対して、外部からのアクセスを制限することができるようになります。

ここで思い出して欲しいのが、ポケモンクラスを作った時に$nameプロパティを定義した時にprivateという修飾子をつけました。

Pokemon.php
//名前プロパティ
private $name;

これをつけると、クラス内でしか操作・呼び出しすることができなくなるのですが、どうしてそのようなことが必要なのでしょうか?

実際にprivatepublicに変えてみます。
また、タイプを表す$typeプロパティも追加します。

Pokemon.php
//名前プロパティ
public $name;
//追加
private $type;

そして、ヒトカゲのインスタンスを生成します。

index.php
$hitokage = new Pokemon('ヒトカゲ','ほのお');

「ほのおタイプ」の「ヒトカゲ」が出来上がりました。

しかし、ここでとある悪い人が名前を「ピカチュウ」に変えてしまいました。
すると…

index.php
$hitokage->name = 'ピカチュウ';
echo '名前は'.$hitokage->name.'です。';
//>> 名前はピカチュウです。

名前が、ピカチュウになってしまい、「ほのおタイプ」の「ピカチュウ」が出来てしまいました。
ゲームとしては、完全にバグです。

こんな感じで、オブジェクトを安全に扱うには、カプセル化を利用して、外部からのアクセスを制限し、中身が不意に書き換えられないようにするのです。

カプセル化のメリットは他にもあるようですので、気になった人は調べてみてください。

継承

継承とは、すでに用意されているクラスの情報を引き継いで、別のクラスをつくることができる仕組みです。
以下のように定義します。

class 新クラス extends 元クラス { }

実際に、ポケモンクラスを継承して、ピカチュウ専用のクラスを作ってみることにします。

Pikachu.php
class Pikachu extends Pokemon{}

これで、ポケモンクラスを継承したピカチュウクラスができました。

続いて、ピカチュウクラスにだけ新しくメソッドを追加してみます。

ピカチュウは世界中で大人気なので、ファンサービスのために波乗りできるようにします。

Pikachu.php
  public function naminori()
  {
    echo 'なみのり'.$this->name;
  }

ポケモンクラスの$nameプロパティをつかうために$this->nameでアクセスしようとしますが、このままではエラーになります。
privateがついているものは継承されないためです。

継承クラスでもプロパティを使えるようにするには以下のように、protectedプロパティを使います。

Pokemon.php
protected $name;

修飾子の種類による継承先での違いは以下の通りです。

  • public:継承可
  • private:継承不可
  • protected:継承可。継承したクラスからは参照できるが、それ以外の外部からは参照不可。

では、実際に追加したメソッドを呼び出してみます。

index.php
$pikachu = new Pikachu('ピカチュウ');
$pikachu->naminori();
// >> なみのりピカチュウ

無事になみのりできました。

ポリモーフィズム

最後にポリモーフィズムです。

ポリモーフィズムとは、同じメソッドでも、オブジェクト毎に異なる動作をすることをいいます。

これについては、実際に例をみたほうがわかりやすいと思います。

ポケモンクラスを継承したピカチュウクラスでは、ポケモンクラスで定義している進化メソッドが使えるようになっています。

Pokemon.php
//進化
public function evolve($evolvedName)
{
    echo "おめでとう!".$this->name."は".$evolvedName."に進化した!";
}

しかし、ピカチュウが進化するには「かみなりのいし」が必要です。
そこで、ピカチュウクラスで新しく進化の条件に進化の石が必要な進化メソッドを定義します。

Pikachu.php
  //進化
public function evolve($evolvedName,$stone = null)
{
  if(!$stone){
      echo '進化するには、進化の石が必要です';
  }else{
      echo "おめでとう!".$this->name."は".$evolvedName."に進化した!";
  }
}

次に、interfaceというものを付け加えます。
interfaceを使うと、クラスが実装する必要のあるメソッドを明示的に指定することができます。

Pokemon.php
interface IPokemon{
  public function evolve($evolvedName);
}

interfaceはimplementsを使って、各クラスに実装することができます。
こうすることで、ポケモンクラスもピカチュウクラスにも進化メソッドが必要なことを明示的に指定することができました。

Pokemon.php
class Pokemon implements IPokemon{}
Pikachu.php
class Pikachu extends Pokemon implements IPokemon{}

さて、最後にピカチュウクラスのインスタンスで進化を試みます。
第二引数に進化の石を渡さないと進化できなくなりました。

index.php
$pikachu->evolve('ライチュウ');
//  >> 進化するには、進化の石が必要です
$pikachu->evolve('ライチュウ','かみなりのいし');
//  >> おめでとう!ピカチュウはライチュウに進化した!

実装サンプルコード

最後に全ての実装コードです。

index.php
<?php
require_once('./Pokemon.php');
require_once('./Pikachu.php');
$hitokage = new Pokemon('ヒトカゲ');
echo $hitokage->evolve("リザード");
echo "<br>";
$pikachu = new Pikachu('ピカチュウ');
$pikachu->naminori();
echo "<br>";
$pikachu->evolve('ライチュウ');
echo "<br>";
$pikachu->evolve('ライチュウ','かみなりのいし');
Pokemon.php
<?php

interface IPokemon{
  public function evolve($evolvedName);
}

class Pokemon implements IPokemon
{
  //プロパティ
  protected $name;

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

  //進化
  public function evolve($evolvedName)
  {
    echo "おめでとう!".$this->name."は".$evolvedName."に進化した!";
  }
}
Pikachu.php
<?php
require_once('./Pokemon.php');

class Pikachu extends Pokemon implements IPokemon
{
  public function naminori()
  {
    echo 'なみのり'.$this->name;
  }

  //進化
  public function evolve($evolvedName,$stone = null)
  {
    if(!$stone){
      echo '進化するには、進化の石が必要です';
    }else{
      echo "おめでとう!".$this->name."は".$evolvedName."に進化した!";
    }
  }
}

まとめ

オブジェクト指向とかクラス構文とか奥が深いなぁと。getterとかsetterとか、その他にもいろいろ触れてみたかったけど今回はこのくらいで。
間違っているとこあったら、コメントやプルリクください。よろしくお願いします。

参考

79
81
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
79
81