【PHP】サブクラスとは?オブジェクト指向の考え方とアクセサメソッドを解説
この記事からわかること
- オブジェクト指向プログラミングとは?
- カプセル化のメリットとアクセサメソッド
- PHPのサブクラスの構文と使い方
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
phpのクラスはオブジェクト指向プログラミングを取り入れています。
ややこしいオブジェクト指向プログラミングの意味とphpでの使い方やメリットなどを備忘録がてらまとめていきたいと思います。
オブジェクト指向プログラミングとは
オブジェクト指向プログラミングとは「オブジェクト」の集合としてシステムの設計・開発を行うことを言います。
ここでいうオブジェクトとはデータ(プロパティ)とメソッドをひとつにまとめた概念のことです。
データとメソッド(手続き)を分割して設計することで、それぞれが独立し再利用がしやすくなるのが大きなメリットです。
基本的なデータはそのまま再利用しメソッドだけを付け替えることで違うオブジェクトを作りあげることができるようになります。
さらにオブジェクト指向プログラミングには様々なメリットがあります。
- カプセル化
- 継承(インヘリタンス)
- ポリモーフィズム(多態性)
これらのオブジェクト指向のメリットがPHPでどのように生きているのかをまとめていきたいと思います。
PHPクラスのオブジェクト指向
まずPHPのclass(クラス)の実際のコードからおさらいしてみます。
クラスはclass命令
で定義した変数(プロパティ)や関数(メソッド)を簡単に再利用できる便利な機能です。
実際のコード
// クラス(オブジェクト)を定義
class Char{
// メンバ変数(プロパティ)を定義
public $lang;
public $greeting;
// コンストラクタを定義
public function __construct(string $lang,string $greeting){
$this->lang = $lang;
$this->greeting = $greeting;
}
// メンバ関数(メソッド)を定義
public function charView(){
echo $this->lang.'での挨拶は'.$this->greeting.'です';
}
}
$obj = new Char('日本語','こんにちは');
$obj2 = new Char('英語','Hello');
$obj->charView();
// 結果:日本語での挨拶はこんにちはです
$obj2->charView();
// 結果:英語での挨拶はHelloです
今回でいうとCharクラス
がオブジェクトそのものになります。
オブジェクト独自のデータ(プロパティ)とメソッドを保持していて、それぞれにアクセスすることができます。
しかしクラスの中のデータ(プロパティ)とメソッドを使うにはまずnew演算子
を使ってインスタンス化しなければなりません。
インスタンス化した後にプロパティに値を格納したりメソッドを呼び出したりすることができます。値を入れ替えたりすることで別の値での再利用が簡単に行うことができます。
関連記事:【PHP】class(クラス)の使い方とは?変数やメソッド、インスタンスの意味
カプセル化
オブジェクト指向の最大のメリットでもあるカプセル化とはカプセルのようにデータや手続きをまとめてしまうことで簡単に外部からアクセスできなくすることです。
アクセスを制限することで意図せぬアクセスで作り上げたオブジェクトを壊されたり、間違った使い方をされないようにできるのがメリットになります。
PHPではこれをアクセス権を使用することで可能にしています。アクセス権はアクセス修飾子というアクセス権の範囲を指定する前置詞を用いて定義することができます。
アクセス修飾子の種類
アクセス修飾子 | 意味 | 英単語の意味 |
---|---|---|
public | 制限なし | 公衆の〜,公開の〜 |
protected | 定義したクラスとサブクラスのみ | 保護した〜,防いだ |
private | 定義したクラスのみ | 私的な〜,内密の〜 |
プロパティやメソッドの前にアクセス修飾子を定義することでそのプロパティやメソッドの公開範囲を設定することができます。これにより周知したくない情報を制限でき、ユーザーに不要な情報を隠蔽することができるのです。
実際のコード
public $var; // メンバ変数の定義
public function AddNumber(){ // メンバ関数の定義
// 処理
}
アクセサメソッド
プロパティをカプセル化(アクセス修飾子を定義)したことでアクセスを制御することができるようになりました。しかしカプセル化の目的の1つである適切な使い方に関しては制限がかけられていません。
それを可能にするのがアクセサメソッドです。アクセサメソッドとはプロパティをセットするメソッド(セッター)とゲットするメソッド(ゲッター)のことです。
先にアクセサメソッドを使用したコードを見ていきます。
アクセサメソッドのコード
class TestResult{
// privateでプロパティを定義
private $subject;
private $score;
public function __construct(){
$this->setSubject("教科");
$this->setScore(0);
}
// 以下アクセサメソッド
// $subjectのゲットメソッド
public function getSubject():string{
return $this->subject;
}
// $subjectのセットメソッド⇨文字列のみ許可させる
public function setSubject($subject){
if(gettype($subject) === "string"){
$this->subject = $subject;
}
}
// $scoreのゲットメソッド
public function getScore():string{
return $this->score;
}
// $scoreのセットメソッド⇨0〜100の範囲の数値のみ許可させる
public function setScore(int $score){
if($score >= 0 && $score <= 100){
$this->score = $score;
}
}
// ここまでアクセサメソッド
public function resultView(){
echo '今回のテストの'.$this->getSubject().'は'.$this->getScore().'点でした';
}
}
$obj = new TestResult(); // 適切な値をセットする場合
$obj->setSubject("英語");
$obj->setScore(96);
$obj->resultView();
// 結果:今回のテストの英語は96点でした
$obj2= new TestResult(); // 不適切な値をセットする場合
$obj2->setSubject(5); // 数値(int)なのでセットされない
$obj2->setScore(96); // 文字列なので致命的なエラーを起こす
// 以下スクリプトの停止
少しややこしいですね。。アクセサメソッドのポイントは2つです。
- セットメソッドで正しい値のみを受け入れるようにできる
- ゲット/セットのどちらかのみを定義することで読み/書き専用にできる
アクセサメソッドの構文(任意ですが...)
- プロパティをprivateで宣言する
- メソッド名をgetXxxx/setXxxxとする
絶対にこの構文を守らないといけないわけではないですが通例的にこのようにしておくのが無難のようです。
今回のTestResultクラス
では教科名は文字列型限定、スコアは0〜100の範囲の数値が渡されることを想定しています。それ以外の値が格納された時には無視したりエラーを起こすことで予期せぬ挙動が起きないようにしています。
ちなみにgettype関数
は引数に指定した値のデータ型を文字列として返す関数です。この関数で引数の値がstring
の時のみプロパティをセットしています。
カプセル化とアクセサメソッドのメリットまとめ
定義したオブジェクトを守ることができる
サブクラスと継承(インヘリタンス)
継承(インヘリタンス)の仕組みを知るにはまずサブクラスの理解が必要になります。
継承(インヘリタンス)とは上位クラスのデータ(プロパティ)とメソッドを下位クラスが引き継ぐことを言い、上位クラスのことをスーパークラス(基底クラス)、下位クラスをサブクラス(派生クラス)と呼びます。
サブクラスの構文
サブクラスを定義する時は以下のようにextendsキーワード
と引き継ぎたいクラス名を記述します。
class サブクラス名 extends 引き継ぐクラス名{
// 通常のクラスと同じ処理コード
}
サブクラスとして定義したクラスはスーパークラスのアクセス修飾子が許可されているプロパティやメソッドを引き継ぐことができます。なので元のクラスに+αで機能を追加したり、似たようなクラスを定義したい時に簡単に機能を追加したクラスが実現できます。
サブクラスを使うメリット
- データやプロパティを再利用できる
- 機能を追加、修正しやすい
- 記述するコードが少なくなる
実際に最初のCharクラス
をスーパークラスとしてサブクラスja_Char
を定義するとこのようなコードになります。
// スーパークラスCharを定義
// 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
public function __construct(string $lang,string $greeting){
$this->lang = $lang;
$this->greeting = $greeting;
echo "スーパークラスのコンストラクタ実行<br>"; // 追加
}
// 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
// サブクラスja_Charを定義
class ja_Char extends Char{
public function charGoodNight(){
echo $this->lang.'での夜の挨拶はおやすみなさいです';
}
}
$obj = new ja_Char('日本語','こんにちは');
$obj->charView();
// 結果:スーパークラスのコンストラクタ実行
// 日本語での挨拶はこんにちはです
$obj->charGoodNight();
// 結果:日本語での夜の挨拶はおやすみなさいです
サブクラスとして定義したクラスも同様にインスタンス化して使用することができます。その際にはスーパークラスのコンストラクタが実行されている点や、サブクラスには定義していないcharViewメソッドも正常に動作していることに注目してください。
オーバーライド
ここではcharGoodNight
というスーパークラスにはない名前のメソッドを定義しましたが、同名のcharView
というメソッドも再定義することができます。これをオーバーライドと言います。メソッドを上書きできるということですね!
class ja_Char extends Char{
// charViewをオーバーライド(上書き)
public function charView(){
echo $this->greeting.'という挨拶は'.$this->lang.'です';
}
}
$obj = new ja_Char('日本語','こんにちは');
$obj->charView();
// 結果:スーパークラスのコンストラクタ実行
// こんにちはという挨拶は日本語です
例えばこのようにスーパークラスのcharView
をサブクラスで上書きして呼び出すと呼び出されるのはサブクラスのcharView
になります。
呼び出す優先順位
- サブクラス
- スーパークラス
メソッドを呼び出す時に最初にサブクラスを探しにいき、あれば呼び出し終了します。なければスーパークラスに遡りあればメソッドを呼び出します。これはコンストラクタでも同じです。
class ja_Char extends Char{
// 追加
public function __construct(string $lang,string $greeting){
$this->lang = $lang;
$this->greeting = $greeting;
echo "サブクラスのコンストラクタ実行
";
}
public function charView(){
echo $this->greeting.'という挨拶は'.$this->lang.'です';
}
}
$obj = new ja_Char('日本語','こんにちは');
$obj->charView();
// 結果:サブクラスのコンストラクタ実行
// こんにちはという挨拶は日本語です
サブクラスのコンストラクタのみが実行され、スーパークラスのコンストラクタは実行されていないことに注目してください。オーバーライドは便利な機能ですが問答無用で上書きしてしまうため使い方に注意が必要です。
final修飾子でオーバーライドを制限する
そのデメリットを制限することができるのfinal修飾子です。アクセス修飾子の後にfinal修飾子をつけることでそのプロパティやメソッドをオーバーライドできないようにすることができます。
class Char{
public $lang;
public $greeting;
// final修飾子をcharViewメソッドに追加
public final function charView(){
echo $this->lang.'での挨拶は'.$this->greeting.'です';
}
}
class ja_Char extends Char{
// final修飾子をcharViewメソッドをオーバーライドをさせようとする
public function charView(){
echo $this->greeting.'という挨拶は'.$this->lang.'です';
}
}
final修飾子のついたメソッドを定義しようとすると致命的なエラーが発生しスクリプトは完全に停止してしまいます。このようにfinal修飾子を使うことで変更されたくないメソッドやいじられたくない機能を保護することができます。
サブクラスの中でスーパークラスのメソッドを呼び出す
スーパークラスに定義したメソッドの根幹はそのままに少しだけ手を加えたメソッドをサブクラスに定義したい時はparentキーワード
を使うと便利です。
class Char{
public $lang;
public $greeting;
public function __construct(string $lang,string $greeting){
$this->lang = $lang;
$this->greeting = $greeting;
echo __CLASS__."コンストラクタ実行<br>";
}
}
// サブクラスを定義
class ja_Char extends Char{
public function __construct(string $lang,string $greeting){
// スーパークラスのメソッドを呼び出す
parent::__construct($lang,$greeting);
echo "サブクラスのコンストラクタ実行<br>";
}
}
$obj = new ja_Char('日本語','こんにちは');
// 結果:Charコンストラクタ実行
// サブクラスのコンストラクタ実行
※__CLASS__
は使用されているクラス名を返す定数です。
今回はコンストラクタの機能を追加してみました。本来コンストラクタは値を出力する目的ではないですが分かりやすさのためにこのようなコードにしています。
インスタンス化したのはサブクラスのみですがスーパークラスのコンストラクタも実行されているのがポイントです。
parentキーワード
の使い方はスーパークラスのメソッド名を::
で繋ぐだけです。
今回はコンストラクタに使用しましたがもちろんの通常のメソッドにも使用できます。
parentキーワード
を使用することでコードの記述量が圧倒的に減り、また修正箇所もスーパークラス1箇所で済むので保守性も高くなります。
オーバーライドのポイントまとめ
- スーパークラスの機能を上書きできる
- final修飾子で上書きを制限
- parentキーワードでメソッドを簡単に流用可能
終わりに
今回はPHPにおけるオブジェクト指向の特徴でもあるカプセル化と継承についてまとめました。
アクセサメソッドやオーバーライドを使うことで開発の時短やデバックのしやすいプログラムを作りやすくなります。
サブクラスとオブジェクト指向の学習について少しでもお役に立てていると嬉しいです。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。