404 motivation not found | t_ishidaのブログ

1月/08

10

PHPのオブジェクト指向

能書き

ちょうど10年前位?Javaの登場辺りから騒がれていて、最近は定着したのかな?あんまりOOの壁とか言われなくなった。あのオブジェクト指向です。最近の言語の例に倣ってPHPもオブジェクト指向の機能が付いてます。はっきり言って、昨日今日プログラム始めた人にとってはあんまり意味の無い話です。とりあえず機能の紹介だけしておきます。

  • プログラムしていたら、ゴチャゴチャになって訳分からなくなった。
  • 一箇所直すと他の箇所でいっぱい問題が出てきて、プログラムが嫌いになった。

オブジェクト指向と言うのはこう言う経験の有る人にこそ、意味のある話です。

対象読者

  • オレ
  • これ読んで理解できる人

アジェンダ

  • 概要
  • オブジェクト指向って何?
  • オブジェクト指向に向いているプログラム
  • オブジェクト指向のおまけ
    • プロパティ
    • メソッド
    • 継承
    • ポリモーフィズム
    • コンストラクタ
    • デストラクタ
    • オーバーロード(PHPでは無かったよね?)

オブジェクト指向概要

よくある、$dog->bow()とかは非常に正しいけど、あんまり意味が無いと思うので、敢えてプログラムとして具体的な説明をします。

オブジェクト指向について、ありのまま話すぜ。
関連する変数と関数を意味のある単位でまとめておき(クラス)、それを一つの変数(オブジェクト)として扱う事。
何を言っているのかわからねーと思うが、俺にも何を言っているのかわからねー。

C言語を扱った事が有る人ならば「変数と関数ポインタの構造体で、その関数からは自身の構造体に格納されている変数を参照する事が出来る機能がオブジェクト指向です」と説明するのが一番楽なんですが、それだと分からない人にはさっぱりわからないので、どうしようかな・・・・。

全然分からないと思いますが、これ以上、具体的な説明は難しいのでソースを見てみましょう。

具体例

例えば一箇所で複数のテキストファイルに書き込みを行う場合、

$text1 = fopen('a.txt', 'a');
$text2 = fopen('b.txt', 'a');

fwrite($text1, 'aaa');
fwrite($text2, 'bbb');

fclose($text1);
fclose($text2);

ファイルNo用の変数をいっぱい用意したり、

ファイルを閉じたり、書いたりと、

なんか似たようなコードがいっぱいになります。

//$thisっていうのは、クラス内で自分自身を参照する時に使う書き方です。
//これ忘れるとハマるので注意しましょう。
class TextFile{          //class + "クラス名"
var $path = '';        //プロパティ(クラスの変数)の定義
var $fno  = '';

//メソッド(クラスの関数)の定義
function TextFile($p) { $this->path = $p; }
function open()       { $this->fno = fopen( $this->path , 'a' ); }
function append( $ln ){ fwrite( $this->fno, $ln );  }
function __destruct() { if( $this->fno ) fclose( $this->fno );  }
}

と言うクラスが有ったとすると。

$text1 = new TextFile('a.txt');
$text2 = new TextFile('b.txt');
$text1->append('aaa');
$text2->append('bbb');

のように書けます。例があんまり良くないからメリットも分かりづらいと思いますが、機能としてはこんなところです。で、ここで、本当はfwriteする度に改行コードも入れたかったので、そのように修正にしようとした場合、

$text1 = fopen('a.txt', 'a');
$text2 = fopen('b.txt', 'a');
//ここと
fwrite($text1, "aaa\n");

//ここが変わった
fwrite($text2, "bbb\n");

fclose($text1);
fclose($text2);

と、二箇所直さなければならない上、メインルーチンそのものに対する修正が入ることとなります。そうすると、テストをする際にメインルーチン全体を流さなければならないので、ぐへぇ~って気分になりますが、

class TextFile{
var $path = '';
var $fno  = '';
function TextFile($p) { $this->path = $p; }
function open()       { $this->fno = fopen( $this->path , 'a' ); }
function append( $ln ){
//ここが変わった
fwrite( $this->fno, "$ln\n" );
}
function __destruct() { if( $this->fno ) fclose( $this->fno );  }
}

と、直す場所が一箇所で済む上、メインルーチンを直す必要が無いので、このクラスが正しく動作するかどうか?をチェックすれば良い事になります。そのため、別個に小さい「このクラスのテストに特化したドライバー」で流せば良いので、テストも楽になります。さらにクラス単体もしくは、そのクラスが使っているクラスさえあれば、どんな場所でも動くので、同じ機能を別のプログラムで使用したい場合には、手を加えずに流用できると言うメリットも有ります。

オブジェクト指向のメリット
  • 修正の影響範囲を限定しやすい(カプセル化)
  • プログラムを他の場所に持っていっても動かし易い(カプセル化 + 再利用性)
  • クラスそのもののテストが楽(カプセル化)

オブジェクト指向に向いているプログラム

で、プログラムの経験者からすれば修正の影響範囲を限定しやすいとか、再利用性がどうのとか、テストが楽とかって、別にオブジェクト指向じゃなくてもよくね?しっかり閉じた関数を作ればイイよねって話だと思う。その答えは「Yes」です。オブジェクト指向でなければならない言語、例えばJavaとかは、必ずオブジェクト指向で有る事を求められますが(求められても、クラス1個にメソッド1個で1万行近いコードを書いてるようなプロジェクトもありますけどね)、PHPやPerlはオブジェクト指向である必要は有りません。オブジェクト指向の適用が最上の選択で有るときに、そうすれば良いと思います。

例えば、データベースを抽象化したい時とかは良いです。最近流行りのORマッパですね。データベースのカタログからテーブル定義を取ってくるってなると、このブログのサンプルで書くにはちょっと大変なので、とりあえずはベタ書きで。

class Table1{
var $flds   = '';
var $keys   = '';
var $values = '';
var $driver = '';
var $tab_nm = 'Table1';
function Table1( $db ){
$this->keys = array('id');
$this->flds = array(
'id'   => array( dat_type => 'int'     , 'length'=> 5  ) ,
'name' => array( dat_type => 'varchar' , 'length'=> 50 )
);
require_once "driver/$db.php";
eval("\$driver = new ${db}()");
}
function __set($name, $value){ $this->values[$name] = $value;         }
function __get($name)        { return $this->values[$name];           }
function save()              { $driver->save($this);                  }
function search($key)        { $this->values = $driver->($this,$key); }
}
class MySQL{
var $con = '';
function MySQL(){
//$conを何らかの形で繋ぐ。
//$con->executeは仮に、SQL文を実行するメソッドとする
//$con->getRecordsは仮に、SQL文の結果をハッシュでとってくるものとする。
}
function save($obj){
$keys      = array();
$fld_names = array();
$values    = array();
foreach( $obj->keys as $key){
$keys[] = "$key = " . fmtValue( $obj->flds[$key][dat_type] , $obj->values[$key] );
}
foreach( array_keys($obj->flds) as $fld ){
if( $obj->values[$fld] ) {
$fld_names[] = $fld;
$values[]    = $this->fmtValue( $obj->flds[$fld][dat_type], $obj->values[$fld] );
}
}
$con->execute( "delete from $obj->tab_nm where " . join( ' and ', $keys ) );
$con->execute(
"insert into $obj->tab_nm (" .
join( ',', $fld_names ) .
") values (" .
join( ',', $values ) . ")"
);
}
function seach( $obj,$key_values ){
foreach( $obj->keys as $key ){
$keys[] = "$key = " . fmtValue( $obj->flds[$key][dat_type] , $key_values[$key] );
}
return $con->getRecords( "select * from $obj->tab_nm where ". join(' and ', $keys ) );
}
function fmtValue( $type , $value ){
if( $type == 'int' )  return $value;
return "'$value'";
}
}

もちろん、これはこのままじゃ使い物になりませんがdriverをDB毎に実装していれば、DBがMySQLだろうと、Oracleだろうと、テキストファイルだろうと、メインルーチンどころか、何もいじらなくても、いけちゃう事が分かると思います。こう言うプログラムにはオブジェクト指向(OOP)は向いていると思います。

逆にオブジェクト指向に向いていないタイプの問題も有ります。例えば、小さいプログラムはオブジェクト指向じゃなくても、既存のデータ構造とアルゴリズムだけでサクッと書いた方が楽でしょう。

<html>
<head>
<title>なんか一覧</tile>
</head>
<body>
<?php
$rs = $con->getRecords("select * from table1";)
<table>
<?php foreach( $rs as $r ){ ?>
<tr>
<td><?=$r[id]?></td>
<td><?=$r[name]?></td>
</tr>
<?php } ?>
</table>
</body>
</html>

オブジェクト指向のおまけ

オブジェクト指向には”継承”と言う機能と、”ポリモーフィズム”と言う機能が付いてます。さらに、普通に呼べば良いモノを変な呼び方する文化も有ります。その辺について色々説明していきます。

プロパティ

オブジェクト指向の世界ではクラスの中の変数の事をプロパティと呼びます。

class foo{
//これがプロパティ
var $bar = 'baz';
}

$x = new foo();

//プロパティの参照
print $x->bar();

オブジェクト指向の世界では変数を参照するのに、

「アクセッサ」と言う関数を通して参照すると言う変な習慣が有ります。

これは値の設定の時にチェックしたり、

値の返却の時に編集したりするのが楽になるためと言われていますが、

多分、ステップ換算の案件の時に、より多くふんだくれるからだと思います。

class foo{
//これがプロパティ
var $bar = 'baz';
function setBar($val){ $this->bar = $val; }
function getBar()    { return $this->bar; }
}

$x = new foo();

//プロパティの参照
$x->setBar('x');
print $x->getBar();

そんな思惑や意図を無視して、PHPには

アクセッサを自動生成するショートカットが有ったりします。

class foo{
//これがプロパティ
var $values = array(
foo => 'Who' ,
bar => 'baz'
);
function __set($name, $value){ $this->values[$name] = $value;         }
function __get($name)        { return $this->values[$name];           }
}

$x = new foo();
//2008/02/28 訂正
//print $x->getbar();
//print $x->getfiz();
print $x->bar();
print $x->fiz();
メソッド

オブジェクト指向教では関数の事をメソッドと呼びます。

教祖様がそう言うので、そう呼ぶことにしましょう。

継承

殆どおんなじだけど、ちょっと違うクラスを作りたい時に使います。

やってることは

  • 元のクラスのファイルを開く
  • 全部コピーする
  • 貼り付ける
  • クラスの名前を変える
  • コンストラクタを削除する
  • 関数を追加する
  • 保存する

と、思っていただいて問題有りません。

もうちょっと色々ありますが、

その辺はなんか有った時調べればOKです。(をい)

class xParent{
function xParent(){
}
function say(){
print 'Parentだよ';
}
}

// class + "クラス名" + extends + "親クラス名"で、
// 親クラスを継承したクラスの定義
class xChild extends xParent {
function xChild(){
}
function sayX(){
print 'Childだよ';
}
}

$c = new xChild;
$c->say();
$c->sayX();

継承する時、オーバーライドって言うのがあります。

親クラスの関数を、上書きする事です。

他は一緒でいいんだけど、特定のメソッドだけ違う動きを

させたい時に使います。

class xParent{
function Parent(){}
function say(){print 'Parentだよ';}
}
class xChild extends xParent {
function Child(){}
//  function sayX(){ print 'Childだよ'; }
function say(){ print 'Childだよ'; }
}

$c = new xChild;
$c->say();

ポリモーフィズム

多態性とか言う奴です。ようは関数名と引数の形式さえ合ってれば、

別のクラスの関数だろうと同じように呼び出せると言う奴です。

厳密な言語だとインターフェースと継承の話が絡むのですが、

PHPでは厳密な部分は関係ないので気にしないようにしましょう。

厳密な部分を知りたい人は、Javaの本とかに嫌らしい程書いてあるので、

そちらを参照して下さい。

class Test1{ function xyz(){ return __CLASS__ . "のメソッドだぴょん。\n"; } }
class Test2{ function xyz(){ return __CLASS__ . "のメソッドだにゃー。\n"; } }
foreach( array( new Test1(), new Test2() ) as $c ) print $c->xyz();

コンストラクタ

newされた時に実行されるメソッドです。

通常は引数を渡したりして、クラス定義時には決め切れない、

動的な要素を初期化するために使用します。

class Test1{
//クラス名と同じにする
function Test1($arg1,$arg2){
}
}
//ここでコンストラクタは呼ばれるよ
$t = new Test1('a','b');

デストラクタ

クラスが破棄されるタイミングで呼ばれるメソッドです。

クラスが破棄されるタイミングと言うのを説明するのは、

処理系によって異なるので難しい(特にガベージコレクタな処理系では)のですが、

どこからも参照されなくなった時。と考えるのが良いでしょう。

//参照が一つ
$o1 = new Class1();

//参照が二つ
$o2 = $o1;

//参照が一つ消えたので残り一つ
$o1 = null;

//参照が完全に消えたので、ここいらでデストラクタが走る
$o2 = null;

オブジェクト指向教におけるお行儀の良いプログラミングでは必須です。

言語によってはメモリリーク防止のために必須です。

通常は、依存クラスのオブジェクトを破棄したりするのに使います。

class Test1{
//こうやって書きます
function __destruct(){ }
}
Share and Enjoy:
  • Digg
  • del.icio.us
  • Google Bookmarks
  • Tumblr
  • email
  • Facebook
  • FriendFeed

RSS Feed

コメントはまだありません。

Leave a comment!

<< 正規表現

ミュージッククリップ >>

Find it!

Theme Design by devolux.org

Tag Cloud