タグ別アーカイブ: オブジェクト指向

クラスベース言語PHPの基本作法

オブジェクト指向のタイプ別分類によれば、JavaScriptが属するプロトタイプベース言語とは異なる、クラスベース言語に含まれるPHP。けれども、そもそもPHPという言語は場当たり的な書き方ができる言語としてブイブイ鳴らしていたもので、ゆえにPHP4以前のヴァージョンでのオブジェクト指向サポートは少し心許ないものでした。

2004年にリリースされたPHP5では、その辺りの弱さを解決し、Javaに近いクラスベースに移行しています。したがって、PHPでのクラスを使った開発の作法というのは、基本的にはJavaのそれに近いものになります。基本的な部分の書き方を見てみましょう。

クラスの宣言とアクセス制限

PHPの基本的なクラス宣言です。

//クラス宣言 someHowClass.php
class someHowClass{
 
//クラスプロパティ
public $somehow = "とにかく";
 
//メソッド
function someHowMethod(){
return "動け!";
}
 
}
 
//別ファイルからの呼び出し・インスタンス化・メソッドへのアクセス
require_once("someHowClass.php");
$somehow = new someHowClass();
echo $somehow->somehow . $somehow->someHowMethod();
 
//結果表示
とにかく動け!

クラス宣言をする場合、classという予約語を使います。クラスプロパティ・クラスメソッドの宣言は、通常の変数宣言や関数定義をclassの括弧内で行うことでできます。この際にプロパティ・メソッドへのアクセス制限を行うことも可能で、その場合に使う修飾子はJavaやSmalltalkと同じく、public、private、protectedがあります。

アクセス修飾子 アクセス可能な範囲
public(初期値) メソッド・プロパティをどこからでも呼び出し可能
private クラス内でのアクセスが可能
protected クラス内および子クラスからのアクセスが可能

コンストラクタ

インスタンスが作成される際の初期値を定義するコンストラクタは、__construct()という関数の処理として書きます。

//クラス宣言 someHowClass.php
class someHowClass{
 
//コンストラクタ
function __construct($value){
$this->value = $value;
}
 
//メソッド
function someHowGetValue(){
return $this->value;
}
 
}
 
//別ファイルからの呼び出し・インスタンス化・メソッドへのアクセス
require_once("someHowClass.php");
$somehow = new someHowClass("動け!");
echo $somehow->someHowGetValue();
 
//結果表示
動け!

コンストラクタの呼び出し時に引数を与えることで、インスタンスの初期値を設定することができます。なお、PHP4の時点でのクラスは、コンストラクタがクラス名と同名のメソッドでした。互換性の為に現在でも、__constructの宣言が無い場合には同名メソッドをコンストラクタとする仕様になっています。

クラスの継承と親クラスのメソッド・プロパティの参照

クラスの継承についても見てみましょう。

//クラス宣言 anyWayClass.php
class anyWayClass extends someHowClass{
 
//メソッドをオーバーライド
function someHowGetValue(){
return $value . "!";
}
 
//メソッドの追加
function anyWayGetValue(){
//親クラスのメソッドの参照
return parent::someHowGetValue();
}
 
}
 
//別ファイルからの呼び出し・インスタンス化
require_once("someHowClass.php");
require_once("anyWayClass.php");
$anyway = new anyWayClass("動け!");
 
//子クラスでオーバーライドしたメソッドの呼び出し
echo $anyway->someHowGetValue();
 
//結果表示
動け!!
 
//子クラスで宣言したメソッドの呼び出し
//(処理内容は親クラスのsomeHowGetValue()メソッドの呼び出し)
echo $anyway->anyWayGetValue();
 
//結果表示
動け!

継承をする場合には、extendsという予約語を使い継承元のクラスを指定します。親クラスのプロパティ・メソッドは自動的に子クラスにも実装されるため、上の例では子クラス内で宣言の無いコンストラクタ(__construct())についても処理が出来ています。
同名プロパティ・メソッドに、子クラスでは違う処理を充てたいという場合には、子クラス内で同名プロパティ・メソッドを宣言すると、そちらが呼び出し時に優先されるようになります。上の例ではsomeHowGetValue()メソッドをオーバーライド(上書き)したため、処理結果が親クラスと変わっています。

一方、子クラスで宣言したものではなく、親クラスのsomeHowGetValue()メソッドを呼び出したくなった場合には、parent::という語をつけて呼び出すことで、子クラス内から親クラスのプロパティ・メソッドの参照が出来ます。

なお、PHPの継承は多重継承を認めない仕様になっています。

PHPのクラスの基本はこのような感じです。その他にinterfaceの定義などもあるのですが、とりあえずは説明をここまでとします。

また、いままでオブジェクト指向に全く触れたことがないという人間にとってはチンプンカンプンな内容だったと思うので、オブジェクト指向の説明記事についてはいずれ改めて書かせていただこうと思います。


JavaScriptがプロトタイプベース言語だということについて

オブジェクト指向言語も決して一枚岩でありません。西の横綱もいれば、西の高校生探偵もいます。このブログで扱う事の多いPHP、JavaScriptなどについて見てみますと、PHPはクラスベース言語というタイプに含まれるのに対して、JavaScriptはプロトタイプベース言語というタイプに含まれます。JavaScriptのプロトタイプベース言語の方が、幾分マイナーですので、こちらの特徴を追ってみましょう。

プロトタイプベース言語には、クラスの概念が無い

オブジェクト指向についてかじった事のある人間ならば、あらゆるものがオブジェクトで、そのオブジェクトには設計図たるクラスが必要で…といったようなオブジェクト指向のイロハに馴染んでいることと思います。一方JavaScriptが属するマイノリティ、プロトタイプベース言語には、オブジェクトの設計図たるクラスは存在しません。それでは、オブジェクトは何を元にして作られるのか、何を実体化してインスタンスをつくるのかという疑問が当然沸き上がる筈でしょう。

実は、JavaScriptにおけるインスタンス化は、既存のオブジェクトを丸々コピーすることによって行われます。コピー元もまた実体をもったオブジェクトなのです。

はい、メモリの無駄遣い発見!JavaScriptはとんだ低級言語だぜ!と思われた方は、オブジェクト指向の意義についてしっかりと理解された方だと思います。既存のオブジェクトをコピーするという方法では、もしプロパティやメソッドの実装部分が100個あったら、インスタンスそれぞれにつき100個を丸ごとコピーしなければなりません。それでは、毎度毎度新しいオブジェクトを一から作成しているのと何ら変わりません。そこで、プロトタイプ言語ではそうした問題を解決する方法として、プロトタイプオブジェクトという概念を用意しています。

JavaScriptでのクラス(仮)宣言

JavaScriptでクラスのようなもの、つまりインスタンスにコピーされるコピー元のオブジェクトを宣言する場合には、変数(クラス(仮)名)への関数リテラルの代入だけで済みます。

var Conan = function(){};

これで終わりです。右側に書かれた無名関数がコンストラクタになりますので、何かしらプロパティやメソッドを設けたい場合には、function(){}の中に書けば大丈夫です。

var Conan = function(powder){
this.powder = powder;
this.pero = function(){
if(this.powder == "青酸カリ"){
alert("これは…青酸カリ!");
} else {
alert("この料理を作ったのは誰だっ!");
}
};
};

これでクラス(仮)Conanにプロパティpowderとメソッドpero()ができました。インスタンスを作成する際に引数を与えることで、プロパティpowderを初期化できます。

var conan = new Conan("青酸カリ");
conan.pero();
 
//結果 これは…青酸カリ!

JavaScriptのプロトタイプ

さて、先ほど挙げた問題に戻ります。このままですと、Conanクラスのインスタンスをconan1,conan2,conan3…とどんどん作っていくことで、その都度powderプロパティとpero()メソッドの宣言が行われメモリの無駄になってしまいます。そこで、JavaScriptのクラス(仮)には暗黙のプロパティであるprototypeというものが用意されており、このプロパティ(正体はオブジェクト)への値の代入を行うことで、プロパティやメソッドの追加が行えるようになっています。

var Conan = function(powder){
this.powder = powder;
};
 
Conan.prototype.pero =  function(){
if(this.powder == "青酸カリ"){
alert("これは…青酸カリ!");
} else {
alert("この料理を作ったのは誰だっ!");
}
};
 
//宣言してもいないConanクラスのプロパティ prototype に値を入れてしまう

これで、先ほどと同じくインスタンスからメソッドを実行できるようになります。

どの辺りがメモリの節約になったのかと言うと、実はインスタンス側にはpero()メソッドがコピーされておらず、インスタンスからpero()メソッドが呼ばれると、インスタンスの持つprototypeオブジェクトへの参照を便りに、prototypeオブジェクト中の同名メソッドを呼ぶ仕組みになっているのです。
つまりどれだけプロトタイプ側にメソッドやプロパティを追加しても、インスタンス側にはプロトタイプへの参照しか残らないので、経済的だというわけですね。

いかがだったでしょうか。決してJavaScriptの属するプロトタイプベース言語が、クラスベース言語に劣った低級なものでないという事が分かったのではないでしょうか。実はプロトタイプベース言語はクラスベース言語へのアンチテーゼとして作られたという経緯があり、クラスベースのオブジェクト指向の弱点である、開発の段階が進むにつれて設計図であるクラスへの設計図としての要求が高くなってくる(→リファクタリングの必要性が出てくる)といったところをうまく解決して、機能の追加をアドホックに行えるという持ち味を醸しています。

ということで、JavaScriptは案外面白い奴です。