オブジェクト指向とは
オブジェクト指向プログラミング
オブジェクト指向プログラミングとは、互いに密接な関連性を持つデータとメソッドをひとつにまとめてオブジェクトとし、それぞれ異なる性質と役割を持たせたオブジェクトの様々な定義と、それらオブジェクトを相互に作用させる様々なプロセスの設定を通して、プログラム全体を構築するソフトウェア開発手法である。
上記引用文で「なるほど!わかった!」となった方は、本記事を読む必要はありません。
プログラミング、特にオブジェクト指向の説明になれば専門用語がかなり多く出てくるため、初心者がただ読んだだけでは理解できないケースがほとんどです。
今回はそんなオブジェクト指向について、よりわかりやすく理解を深め「便利じゃん!」となるように、日本の代表的ゲームである「ポケモン」のピカチュウの力を借りてご説明します。もしポケモンのゲームをやったことがない、全く知らないという人は、ブラウザをバックして別のオブジェクト指向について説明が書かれている記事を参考にするか、一度初代ポケモンをプレイしてから出直してください。
ピカチュウを作る
オブジェクト指向では、まずクラスを作成することから始まります。今回はピカチュウをという存在を定義するために、Pikachuというクラスを作成します。
<?php
// ピカチュウ
class Pikachu
{
}
これでピカチュウが出来上がりました。中身は空っぽですので、これから1つずつ理解しながら詰め込んでいきます。
オブジェクト指向では、まずこのようにクラスを作ることで、ピカチュウという存在そのものを定義します。ゲームを進めていく中で、ピカチュウは1匹だけではありませんよね(ややこしいのでピカチュウ版は除きます)。好きな人は、2匹、3匹と捕まえるかも知れません。さらに、技構成全く同じではなかったり、能力値やレベルも個体によってばらつきが出ます。
しかし中身が違っていても、その全てが「ピカチュウ」で有ることには代わりありません。その「ピカチュウ」という存在事態を定義するのがクラスなのです。
個体を作成(インスタンス)
クラスを作成しても、それは「ピカチュウ」という存在事態が定義されたに過ぎず、実体はありません。これは初期の「ミュウ」という存在で考えてみるとわかりやすいかも知れません。
初代のポケモンが発売された当初、「ミュウ」という存在自体は確かにありました。しかし、ゲーム上で捕まえることが出来ず(バク・裏技を除く)、実体はどこにも有りませんでした。しかし、イベントで配布するということによって、初めてその実体が確認され、ユーザーの元に届き、ミュウを使うことができるようになりました。この実体こそが「インスタンス」です。
それでは、ピカチュウの実体(インスタンス)を作ってみましょう。
<?php
// ピカチュウ
class Pikachu
{
}
$pikachu = new Pikachu;
「new クラス名」とすることで、ピカチュウのインスタンスを作成することができます。$pikachuという変数に代入しているので、$pikachuはピカチュウです。もちろんピカチュウは1匹限りの存在ではないので、複数匹呼び出すことが出来ます。
<?php
// ピカチュウ
class Pikachu
{
}
// トキワの森
$pikachu1 = new Pikachu;
$pikachu2 = new Pikachu;
$pikachu3 = new Pikachu;
$pikachu4 = new Pikachu;
$pikachu5 = new Pikachu;
$pikachu1〜5までの5匹分のインスタンスを作成しました。この全てがピカチュウですが、それぞれは別個体です。もし$pikachu1に10万ボルトという技を覚えさせたとしても、それに連動して$pikachu2や3も同じように覚えることはありません。
名前の設定(プロパティ)
では、ピカチュウのクラスに情報を入れていきましょう。
まず最初に必要になるのは名前です。正式名称として、ピカチュウという名前をクラス内に設定しましょう。
<?php
// ピカチュウ
class Pikachu
{
public $name = 'ピカチュウ';
}
$pikachu = new Pikachu;
echo $pikachu->name;
echoの出力結果は以下の通りです。
ピカチュウ
期待した通りの結果が出力されましたね。
それでは、クラス内に追加した部分を見みましょう。
public $name = 'ピカチュウ';
クラス内では変数が定義できます。これをプロパティ呼びます。ここに値を代入することで、ピカチュウのクラス内では$name($this->name)をピカチュウとして扱うことが出来ます。
※クラス内の$nameの手前に付けたpublicについては後ほど解説します
インスタンス化(実体化)したピカチュウから名前を教えてもらうためには、クラス内に定義したプロパティにアクセスする必要があります。
$pikachu = new Pikachu;
echo $pikachu->name;
ピカチュウというインスタンスが格納されている$pikachuから、->を使用してnameを取り出しています。こうしておけば、インスタンスさえ呼び出されていれば、いつでも名前を確認することが可能です。
ニックネームをつける(コンストラクタ)
ポケモンは捕まえた際に、ニックネームを付けることができます。同じポケモンを複数体育てるのであれば、その判別のためにもニックネームがなければひと目で見分けが付きません。今回はポケモンを捕まえる過程が無いので、インスタンスを作成した時点でニックネームを設定できる仕様にしましょう。そのために使うのがコンストラクタです。
<?php
// ピカチュウ
class Pikachu
{
public $name = 'ピカチュウ';
public $nickname;
public function __construct($nickname=null)
{
if(is_null($nickname)){
$this->nickname = $this->name;
}else{
$this->nickname = $nickname;
}
}
}
$pikachu = new Pikachu('ピカイチ');
echo $pikachu->nickname;
echo $pikachu->nicknameの出力結果は以下の通りです。
ピカイチ
クラスの使い方に慣れていない方は、そろそろややこしくなってきましたね。でも安心してください。1行ずつしっかり理解していけば問題有りません。
まず、クラス内にニックネームを格納するためのプロパティを準備しました。
public $nickname;
初期状態ではニックネームは設定されていないので、プロパティだけを宣言します。そして、次に出てきている関数が今回の注目ポイントです。
public function __construct($nickname=null)
クラス内で使える変数(プロパティ)があるように、クラス内で使える関数もあります。これをメソッドと言います。メソッドについては次項で詳しく解説します。
今回クラス内で使っている__constructは特別なものです。なぜなら、このクラスをインスタンス化(実体化)した際、自動的に動いてくれるからです。クラスの呼び出し部分から動きを確認してみましょう。
$pikachu = new Pikachu('ピカイチ');
先程の呼び出し方と少し変わって、new Pikachuのあとにカッコと値がありますね。これは引数です。ピカチュウというクラスを呼び出す際に、第1引数として「ピカイチ」という値を指定しました。
クラスが呼び出されると、先程説明したようにクラス内にある__constructが自動で動きます。第1引数で指定された「ピカイチ」という値は、ここで受け取ることができます。
__constructの引数として、$nickname=nullが指定されています。これも関数の引数と同じで、もしクラスを呼び出す際にニックネームの指定がなければ、nullを初期値として格納します。今回は「ピカイチ」という値が指定されているので、$nicknameにはピカイチが格納されました。
では、__constructの中身を確認してみましょう。
if(is_null($nickname)){
$this->nickname = $this->name;
}else{
$this->nickname = $nickname;
}
ここで1つ複雑なポイントを解消しておきましょう。
__constructで引数で受け取る値を格納する変数を$nicknameとしました。しかし、クラス内ではプロパティとして同じように$nicknameが宣言されています。これは名称が同じであっても、同じものではありません。なぜなら、クラス内で定義したプロパティは、$nicknameとしては使用せずに、$this->nicknameというように使用するからです。
$thisはクラスのことを指しています。今回であればPikachuというクラスです。クラス内のプロパティである$nameや$nicknameにアクセスする場合は$this->nameや$this->nicknameとしなければなりません。そのため、__construct内の$nicknameは別物なのです。更にこの引数として受け取った$nicknameは、プロパティと違って__construct内でしか使用できません。
上記を踏まえて、先程の処理を1行ずつ確認見ていきましょう。
if(is_null($nickname)){
引数で受け取った$nickname(ピカイチ)をis_nullで判定して条件分岐(if)しています。今回はピカイチという値が引数で指定され、$nicknameに格納されているので、is_nullはfalse(否)となり、else内の処理に入ります。
}else{
$this->nickname = $nickname;
}
else内では$this->nicknameに$nicknameを代入しています。$this->nicknameはピカチュウクラスのプロパティです。初期値が設定されていないため、現在は何も格納されていません。そこに代入する$nicknameには引数で受け取った「ピカイチ」が格納されています。$this->nickname(空)に$nickname(ピカイチ)を代入することで、nicknameというプロパティに「ピカイチ」という値をセットしているのです。
上記の処理がクラスを呼び出した時点「$pikachu = new Pikachu(‘ピカイチ‘);」で行われています。そのため、インスタンス化(実体化)した$pikachuからニックネームを取り出すと、ピカイチという値が取得できるのです。
echo $pikachu->nickname;
# 出力結果
ピカイチ
では、クラスを呼び出す際に引数を指定しなかった場合を見てみましょう。
<?php
// ピカチュウ
class Pikachu
{
public $name = 'ピカチュウ'; #
public $nickname;
public function __construct($nickname=null)
{
if(is_null($nickname)){
$this->nickname = $this->name;
}else{
$this->nickname = $nickname;
}
}
}
$pikachu = new Pikachu;
echo $pikachu->nickname;
# 出力結果
ピカチュウ
これは、__construct内のif文でis_null($nickname)がtrue(正)を返したからです。
if(is_null($nickname)){
$this->nickname = $this->name;
is_null内の$nicknameは引数の値が格納されていましたね。指定されていなければ初期値「$nickname=null」として設定されたnullが入っているので、if文内の「$this->nickname = $this->name」が実行されます。
nicknameのプロパティに、nameのプロパティ(ピカチュウ)がセットされています。ポケモンのゲームでもニックネームを指定しなければポケモンの正式名称がセットされるので、仕様としては同じになりました。
このように、__constructはクラスを呼び出したとき、最初に自動的に実行されるメソッド(関数)だということです。合わせて、プロパティと変数との違い($this->nicknameと$nickname)についても、ざっくりで構いませんので覚えておきましょう。
最後に__construct内をPHP7仕様に簡略化しておきます。
※処理内容はほとんど変わりませんので、is_nullを使った方法の方がわかりやすい人はそちらを参考にしてください。null合体演算子の場合は空文字(”)を許可してしまいますが、今回はそこまで厳密にしていしないものとします
<?php
// ピカチュウ
class Pikachu
{
public $name = 'ピカチュウ';
public $nickname;
public function __construct($nickname=null)
{
$this->nickname = $nickname ?? $this->name;
}
}
null合体演算子について内容を詳しく知りたい人は、以下の記事を参考にしてください。
技を取得(メソッド)
ポケモンといえば技を習得できますね。初代ピカチュウの場合、最初は「でんきショック」と「なきごえ」という技を覚えています。これはピカチュウという存在事態に共通していることなので、クラスへ設定します。
private $move = [
'でんきショック',
'なきごえ',
];
今回はpublicではなくprivateでプロパティを準備しました。こちらも後ほど解説します。
次に、技の一覧を取得するために、クラス内で使用できる関数(メソッド)を作成します。
// 覚えている技の一覧を取得する
public function getMove()
{
return $this->move;
}
__constructは特別なメソッドでしたが、自ら用途に合わせて使いたい関数をクラス内に準備することができます。これがメソッドです。
PHPでは変数や関数を必要に応じて作ることができますよね。これを、そのクラス内で使う用として準備するものがメソッドになります。
では、技を増設した処理と出力結果を確認してみましょう。
<?php
// ピカチュウ
class Pikachu
{
public $name = 'ピカチュウ';
public $nickname;
private $move = [
'でんきショック',
'なきごえ',
];
// クラス呼び出し時(インスタンス作成時)に自動で最初に実行される処理
public function __construct($nickname=null)
{
$this->nickname = $nickname ?? $this->name;
}
// 覚えている技の一覧を取得する
public function getMove()
{
return $this->move;
}
}
$pikachu = new Pikachu;
var_export($pikachu->getMove());
# 出力結果
array (
0 => 'でんきショック',
1 => 'なきごえ',
)
ピカチュウの技一覧が取得できましたね。では、今回追加したgeMoveというメソッドを確認してみましょう。
// 覚えている技の一覧を取得する
public function getMove()
{
return $this->move;
}
内容はいたって簡単です。getMoveを呼び出せば、技の一覧を返す「retrun $this->move」という内容です。$this->moveには「でんきショック」と「なきごえ」を配列として入れているので、それを返却してくれています。
メソッドの呼び出し方法も、プロパティと同じように->を使用します。
$pikachu->getMove()
インスタンスが作成されていれば、そのクラス内で準備しているメソッドを呼び出すことができるのです。
アクセス修飾子
それでは気になっていたpublicやprivateについて、最後にまとめておきましょう。
プロパティやメソッドの手前についていたこれらは、アクセス修飾子と言います。PHPでは以下の3種類が用意されています。
-
public
クラス内、クラス外のどこからでもアクセスが可能 -
protected
同じクラス及び子クラスからアクセスが可能 -
private
同じクラス内からのみアクセスが可能
※protectedについては今回無視しましょう。混乱しないためにも、まずはpublicとprivateを押さえて置けば十分です。
publicは、どこからでもアクセスが可能なプロパティ、メソッドだということを宣言します。例えば、$pikachu->nameでピカチュウという値が取得できるのもpublic $nameと宣言しているおかげです。
それに対して、先程$moveはprivateと宣言しました。その結果、nameとは違い$pikachu->moveでは取得することができません。
<?php
// ピカチュウ
class Pikachu
{
public $name = 'ピカチュウ';
public $nickname;
private $move = [
'でんきショック',
'なきごえ',
];
// クラス呼び出し時(インスタンス作成時)に自動で最初に実行される処理
public function __construct($nickname=null)
{
$this->nickname = $nickname ?? $this->name;
}
// 覚えている技の一覧を取得する
protected function getMove()
{
return $this->move;
}
}
$pikachu = new Pikachu;
$pikachu->move;
# 出力結果
PHP Fatal error: Uncaught Error: Cannot access protected property Pikachu::$move in ...
これでは困るため、技の一覧を取得するためのgetMoveというメソッドを作成したのです。
しかし、ここで1つ疑問が残ります。
結局取得できるようにするのであれば、privateではなくpublicで良いのでは?
そう思いますよね。
しかし、それではいけません。なぜなら、アクセスができるということは、取得だけではなく「書き込み」も出来てしまうからです。
<?php
# moveのアクセス修飾子をpublicに変更した場合
// ピカチュウ
class Pikachu
{
public $move = [
'でんきショック',
'なきごえ',
];
// 覚えている技の一覧を取得する
public function getMove()
{
return $this->move;
}
}
$pikachu = new Pikachu;
$pikachu->move = [
'かえんほうしゃ',
'ハサミギロチン',
];
var_export($pikachu->getMove());
# 出力結果
array (
0 => 'かえんほうしゃ',
1 => 'ハサミギロチン',
)
このように、簡単に外部から書き込みが出来てしまうことはエラーや予期せぬ結果を招く危険性があります。そうならないためにも、アクセス修飾子を使って制御する必要があります。
これを踏まえて、他のアクセス修飾子も見直したクラスがこちらです。
<?php
// ピカチュウ
class Pikachu
{
private $name = 'ピカチュウ';
public $nickname;
private $move = [
'でんきショック',
'なきごえ',
];
// クラス呼び出し時(インスタンス作成時)に自動で最初に実行される処理
public function __construct($nickname=null)
{
$this->nickname = $nickname ?? $this->name;
}
// 正式名称を取得する
public function getName()
{
return $this->name;
}
// 覚えている技の一覧を取得する
public function getMove()
{
return $this->move;
}
}
ニックネーム(nickname)は変わってしまっても大きく支障がありませんので、publicのままにしていますが、正式名称(name)は変えられると一大事ですのでprivateへ変更しました。取得出来ないままでも困るので、技と同様にgetNameというメソッドで取得できるようにしています。__constructはpublicしか指定できませんので、変更は不要です。
このように、外部から書き込みしても良いか、呼び出しが出来ても良いかなどを考えながらアクセス修飾子を設定していきます。
まとめ
いかがだったでしょうか。
今回は「【PHP】ピカチュウから学ぶオブジェクト指向 〜入門編〜」をご紹介しました。
今回取り上げたピカチュウのように、決まった存在を定義する際にはクラスを使うと管理がしやすくなり、開発がスムーズになります。
オブジェクト指向への理解がまだまだ甘い、これからPHPを本格的に学習しようと考えている方は、ぜひ参考にしてくださいね。
第2回はコチラ(クラス継承編)
PHPポケモンのプレイはコチラ