PHPなオブジェクト指向入門vol.3(後半)

前回のつづきです。PHPなオブジェクト指向入門 vol.3における継承MVCサンプルソースの続きです。

なお、肥満度の計算方法、評価方法は肥満度の計算を参考にさせていただきました。


CheckBMI.class.php

<?php
// CheckBMIクラス
class CheckBMI{
	var $BMI = 1;
	var $tall = 1;
	var $weight = 1;
	var $array_error = array();//各種エラーを格納する配列
	function CheckBMI($tall, $weight){ //コンストラクタ
		/**
		 * アクセサメソッドを介して値をセット
		 */
		$this->SetTall($tall);
		$this->SetWeight($weight);
		$this->CalculateBMI();
	}
	function SetTall($tall){
		if(is_numeric($tall)){
			if($tall < 3){
				$this->tall = $tall;//引数$tallをプロパティ(メンバ変数)にセット
			}else{
				$this->array_error[] = "身長の単位はメートルです。センチメートルで入力していませんか?";
			}
		}else{
			$this->array_error[] = "身長には数値を入力してください。";
		}
	}
	function SetWeight($weight){
		// 単純な if 〜 else 〜文は3項演算子を使うと少しスッキリします。
		(is_numeric($weight)) ? $this->weight = $weight : $this->array_error[] = "体重には数値を入力してください。";
	}
	function CalculateBMI(){
		$this->BMI = $this->weight / ($this->tall * $this->tall);
	}
	function GetBMI(){//小数点以下1桁で丸めたBMI値を返すメソッド
		return round($this->BMI, 1);
	}
	function IsError(){//エラーがあるならエラー配列(TRUE)を返すメソッド
		if(count($this->array_error) > 0){ //何かしらのエラーがセットされている
			return $this->array_error;
		}else{
			return false;
		}
	}
}
?>

このクラスは入力フォームから身長と体重の値を受け取り、肥満度を計算するクラスです。前々回のPHPなオブジェクト指向入門 vol.2ものを少し簡素化しただけです。(実際に運用するとなると、意図しない入力値に対応させなければならない箇所がありますが、本題とズレるため省略してます。ゼロで割り算してしまったりと、チェックが甘すぎです...。ちなみにこういうときに有効な「例外」もPHP4にはありません。)


さて、本題のクラスの継承のサンプルソースです。CheckBMI_Plus では スーパークラス(親クラス)の機能を拡張・変更しています。


CheckBMI_Plus.class.php

<?php
/**
 * CheckBMIを継承したクラスCheckBMI_Plusを定義
 * CheckBMI_Plusクラスは親クラス(CheckBMI)のすべてのプロパティとメソッドを再度記述することなく使用できます。
 * 親クラス(スーパークラス)より子クラス(サブクラス)の方が多くのことをできることに注意。サブの方が多機能なんです...。
 */
include('CheckBMI.class.php');
class CheckBMI_Plus extends CheckBMI{
	function CheckBMI_Plus($tall, $weight){
		/* 親クラスのコンストラクタを呼び出す。これにより、$this->SetTall, $this->SetWeigh, $this->BMI がセットされる */
		parent::CheckBMI($tall, $weight);//親クラスのコンストラクタ(PHP4でも parent:: を使えたっけ?)
		//$this->CheckBMI($tall, $weight);//このように記述してもよい
	}
	function JudgeBMI(){//判定メッセージを返すメソッドを追加
		switch(true){//switch文の少し特殊な使い方
			case $this->BMI >= 40 : $msg = "肥満度4";break;
			case $this->BMI >= 35 && $this->BMI < 40 : $msg = "肥満度3";break;
			case $this->BMI >= 30 && $this->BMI < 35 : $msg = "肥満度2";break;
			case $this->BMI >= 25 && $this->BMI < 30 : $msg = "肥満度1";break;
			case $this->BMI >= 18.5 && $this->BMI < 25 : $msg = "普通体重";break;
			default : $msg = "低体重";//18.5未満
		}
		return $msg;
	}
	function GetBMI(){//親クラスのメソッドをオーバーライド(メソッドの名前はそのままに機能を変更)
		return round($this->BMI, 3);//親クラスでは小数点以下1桁としたが、小数点以下3桁で返すよう仕様変更(オーバーライド)
	}
}
/**
 * ためしに function GetBMI(){ } をコメントアウトすると、親クラスの GetBMI() メソッドが機能するため、小数点以下1桁の値を返すはずです。
 */

?>


この CheckBMI_Plus クラスが index.phpインスタンス化され、モデル(MVCのM)として実際に使用されます。index.phpについては前回(PHPなオブジェクト指向入門 vol.3)を参照してください。


以上でクラスの継承のサンプルソース終了です。としたいところですが、もう一点だけ。「クラスの機能を拡張したいだけなら、必ずしも継承を使わなくて良いことも多い。ということを書いた書籍が少ないため、オブジェクトコンポジション(と呼ぶらしい)のサンプルソースです。


継承を使うことなく、先の CheckBMI_Plus.class.php と同様に CheckBMI クラスを拡張するサンプルソースです。特に難しいことはなく、クラス内で他のクラスのオブジェクト(インスタンス)を使うだけです。

<?php
include('CheckBMI.class.php');
/**
 * 他のクラスのメソッドを拡張したいだけなら継承を使わない手法(オブジェクトコンポジション)もある。
 * 継承は1クラスの拡張しかできないが、オブジェクトコンポジションなら複数のオブジェクトのメソッドを使用できる。
 * 親クラスに(子クラスにとっては)不要なメソッドが多数ある場合にはオブジェクトコンポジションの方がよいこともある。
 */
class CheckBMI_Plus_2{
	var $obj;
	function CheckBMI_Plus($tall, $weight){
		//オブジェクトコンポジション(かっこいい響きだが要するに他のクラスのインスタンスを使うだけ!)
		$this->obj = new CheckBMI($tall, $weight);
		/**
		 * 複数のオブジェクトをnewすれば、“多重継承”のようになるかもしれませんが、安易な使用は絶対にやめましょう。
		 * newするオブジェクトにいくつもの変更があったら、それはもう大変なことになります...。
		 */
	}
	function JudgeBMI(){//判定メッセージを返すメソッドを追加
		$BMI = $this->obj->GetBMI();
		switch(true){//switch文の少し特殊な使い方
			case $BMI >= 40 : $msg = "肥満度4";break;
			case $BMI >= 35 && $BMI < 40 : $msg = "肥満度3";break;
			case $BMI >= 30 && $BMI < 35 : $msg = "肥満度2";break;
			case $BMI >= 25 && $BMI < 30 : $msg = "肥満度1";break;
			case $BMI >= 18.5 && $BMI < 25 : $msg = "普通体重";break;
			default : $msg = "低体重";
		}
		return $msg;
	}
	
	/**
	 * 親クラスの設計が悪いと拡張はしやすくても変更(オーバーロード)はしにくいときがあるかもしれません。
	 * CheckBMIクラスの設計が悪いため、拡張するメソッドは容易でも変更するメソッドはうまくいきません。
	 */
	funciton GetBMI(){
		//return round($this->obj->GetBMI(), 3); //CheckBMIクラスで小数点以下1桁に丸めて返す設計をされているのでこれでは意味がない。
		/**
		 * PHP4では全プロパティがpublicなので以下のようにして、凌ぐことは可能だが、もし、privateだったなら軽く焦ります...。
		 * => [教訓] 1つのクラスに頑張らせすぎると、クラスを使い回す局面で意外な苦労をします。
		 */
		return round($this->obj->BMI, 3); //プロパティを直接取り出した。
		
	}
}
?>

以上で終了です。ごく簡単なアプリなのに長文となってしまい、読みにくくなってました。<( _ _)>