オブジェクト指向って何?

 新しめ(といってもオブジェクト指向という言葉も登場してだいぶ経つ)のプログラミング言語には、オブ ジェクト指向の機能を取り入れたものが多い。

 ではそのオブジェクト指向というのを使うと、何がどう便利なのだろう??

 ここで作っているのは物理シミュレーションなので、物理シミュレーションを作る時の必要性から考えてみよう。

一つのもの(オブジェクト)はたくさんの属性を持っている

物理シミュレーションを行うということは例えば物体の運動をシミュレートするわけだが、そのとき我々は

などなどを知らなくてはいけない。

オブジェクト指向のための機能を持たない言語で「物体Xの運動」をプログラムしようとすると、上のそれぞれのために、

と、せっせと変数を作っていく必要がある。

↓のような感じにだ。

		var x;
		var y;
		var vx;
		var vy;
		var m;
		var q;
この var というのは「変数宣言」を表す言葉(javascriptではこうやって変数を宣言する。型指定は必要ない)。今のところは、単に「ああ変数作ってるのね」と思って見ていてくれればよい。

 しかし、これらの変数がたくさん出て来ると、面倒なことが沢山出て来る。例えばこの物体の位置を計算する「関数」があって(しかも物体の位置を計算するには現在位置x,yと速度vx,vyと質量と電荷、そしてもちろん現在時刻を全部知る必要があったりすると)その関数を呼び出すには

		calcPostion(x, y, vx, vy, m, q,t);

のように全ての属性(最期のをその関数(calcPosition)に渡してやらなくてはいけない。

「一つの物体が持っている属性」は一つにまとめておきたい。

という願望が出てくる。

たとえば、「物体X」を表す、その名も「X」という変数だけを、

		calcPositon(X,t);

と渡せばいいなら楽ではないか(時間は物体の属性ではないから別である)。

この、「ある物体の持つ属性はひとまとめにする」いやむしろ、「変数Xが物体そのものを表現している」ようにしよう、というのがオブジェクト指向の第一歩となる。

もの(オブジェクト)は一個作ったら同じものをまた作りたくなるものだ

「物体X」しか出てこないのならいいが、物理シミュレーションを作っていれば2個めの物体を出したくなる。「物体Y」も出て来ると、

のように物体X用の変数を用意した後で、物体Y向けに

と、同じようにせっせとまた変数を一組作らなくてはいけない(物体Xのx座標も物体Yのx座標もどっちもxと いう変数にしてしまったら、プログラムはまともに動かない)。

さらに、たとえば物体Xが等速直線運動しているのだとすると、

		X_x += X_vx*t;
		X_y += X_vy*t;

のようなコードを書きたくなる。

「+=」というのは左辺の変数に右辺の変数を足す、という演算子で、

左辺 += 右辺;

は、

左辺 = 左辺 + 右辺;

と同じこと。

 これも、Yに対してはまた、

		Y_x += Y_vx*t;
		Y_y += Y_vy*t;

と書き、次にZという別の変数が現れたらまた、

		Z_x += Z_vx*t;
		Z_y += Z_vy*t;

と何度も書くはめになる…のはなんとも面倒くさい。

こういうパターン的なコードは、一回書いたらもう書かなくていいようにできませんかね??

という願望を叶える方法としても、オブジェクト指向は使える。

「そんなの、カット&ペーストして置換すりゃいいじゃないですか」と思う人もいるかもしれない。
 しかし考えてみて欲しい。後で「あ、この運動の式をちょっと変えよう」と思ったとき、カット&ペーストした何個ものコピーをまたせっせと変えていかなくてはいけない。それはたいへんだし、何より長いファイルのどこを変えればよいのかを探さなくてはいけなくなる。

 そしてもう一つ最悪な状況が起こり得る。それは「他人の書いたコードを直さなくてはいけない場合」である。その状況を思えば、「同じパターンのプログラムは一回書いてあればよい」ことの有難味がわかる。

自分一人でプログラムしている時でも同じである。「半年前の(下手すると一週間前でも)自分は赤の他人だ」 ということは心得ておこう。

 オブジェクト指向の利点の一つ(まだまだ一つ目だ)は、この願望を叶えてくれることだ。では、オブジェクト指向言語が使っている解決法を以下で説明していこう。

ものに「属性」を与える

 多くのオブジェクト指向言語では、Xという変数オブジェクトに

		X.x
		X.y
		X.vx
		X.vy

のように記号「.」(ピリオド)をつけてその変数の属性(プロパティと呼ぶ)を持たせることができる。

 それぞれのプロパティには数字や文字列を代入したり、その数値を使って計算したりできるので、

		X.x = X.vx * t

のように書くことができる。

ただし、これだけなら例えばCには「構造体(struct)」というのがあって、複数の属性をひとまとめにする機能はついている。

 たとえば、

		function plus_vt(X,t) {
			X.x += X.vx*t;
			X.y += X.vy*t;
		}

のような関数を一回書いておけば、

		plus_vt(Y,t);
		plus_vt(Z,t);

と書くことでYやZに対してこの計算をやってくれる(当然、YやZはxやらvxやらのプロパティを持ったオブジェクトでないとエラーが出る)

 プロパティという機能がなかったとしても、

		function plus_vt(x,y,vx,vy,t) {
			x += vx*t;
			y += vy*t;
		}

のようにすれば、と思うかもしれないが、これだと

		plus_vt(Y_x,Y_y,Y_vx,Y_vy,t);

のように冗長な書き方をしなくてはいけない。

「どのように計算するのか?」も、もの(オブジェクト)に背負わせたい

 ここまでで、物体(オブジェクト)を表す変数が属性を持っていればいろいろ便利だ、ということを説明してきたが、さらに変数だけでははなくfunction(「関数」と訳すのが普通だが、ここは「機能」と訳す方が実態に近いかもしれない)も物体(オブジェクト)に持たせる。オブジェクト指向言語の多くでは「メソッド」とか「メンバ関数」などという方法でそれを実現するのだが、javascriptは「プロパティに関数を持たせる」という方法でこれを実現する。

		X.plus_vt=function(t) {
		  this.x += this.vx * t;
		  this.y += this.vy * t;
		}

と定義しておけば、

		  X.plus_vt(0.1);

と呼び出すことで0.1秒の時間経過を起こしてくれる。

 しかし、これではYやZに対してはまたコードを書かなくてはいけなくなり、不経済である。

 どうすればよいか、ここで使っているサンプルプログラムでも使われている手法で説明しよう。

「クラス」を作る

 オブジェクト指向プログラミングでは、同じ種類のオブジェクトを作るときには、まずそのオブジェクトの「型」である「クラス」を作り、その「クラス」に属するオブジェクトを作る。「クラス」はいわばオブジェクトの鋳型である。

ややこしいことに、javascriptには「クラス」にあたる機能があるのに「クラス(class)」という名前は使っておらず、functionとして使うことになっている。

 例として、Vectorというクラスを考えよう。名前の通りベクトル(今考えているシミュレーションは2次元の物理現象だから、2次元ベクトル)である。まずはvector.jsの最初にあるVectorというクラスの定義を見てみよう。

// 2次元ベクトル
var Vector  =  function (xx, yy) {
	this.x  =  xx;
	this.y  =  yy;
};

 まずこうして「Vector」というのが定義されていると、この後、a=new Vector(3,5);のようにnewという命令でVectorクラスを作ることができる(その新しいベクトルの変数の名前が、ここではa)。以後、a.xがaのx成分、a.yがaのy成分で、最初の、a=new Vector(3,5);によりx成分は3に、y成分は5になっている(上に引用したコードのおかげ)。

このように「どんなふうにVectorを作るか」を記述した部分のことを「コンストラクタ」と呼ぶ。

 さて、ベクトルが定義できたら、そのベクトルを足したり引いたり内積をとったり外積をとったりしたくなるだろう。それも一度書いておけば後は再利用できるようにしておく。そのためには、

Vector.prototype = {
	copyFrom: function (V) {this.x = V.x; this.y = V.y; },
	// v.copyFrom(v2)で、vがv2のコピーとなる。
	// 【ベクトルなどの代入に関する注意】
	// javascriptではプリミティブ型(整数や実数など)以外の変数(オブジェクト)に対しては、
	//	 =  による代入はコピーではなく、一つの実体を共有することになる。
	// たとえば(Vectorはプリミティブじゃないから)、v = v2; のようにすると、
	// vとv2が「同一物の別名」のようになってしまい、
	// 代入後にどちらかを変化させるともう一方も変化してしまう(実体が一つしかないと思えば当然だ)。
	// v = copyFrom(v2); ならそうならない。
	makeCopy: function () { return new Vector(this.x, this.y); },
	// v2 = v.makeCopy()で、vがv2のコピーとなる。
	// ベクトルを引数にして関数を呼び出すとき、呼び出す関数にそのベクトルを変更してほしくないときに、
	// f(v.makeCopy());のようにして呼び出すとよい。
	setXY: function (xx, yy) { this.x = xx; this.y = yy; },
	// v.add(v1)で、v = v+v1
	add: function (V) {this.x +=  V.x; this.y +=  V.y; },
	// v.sub(v1)で、v = v-v1
	sub: function (V) {this.x -=  V.x; this.y -=  V.y; },
	// w = v.add(v1)で、w = v+v1

途中省略

	rot90: function () {
		var newx = -this.y;
		this.y = this.x;
		this.x = newx;
	},
	rot270: function () {
		var newx = this.y;
		this.y = -this.x;
		this.x = newx;
	}

};

のように「プロトタイプ(prototype)」を定義する。

 Vector.protypeを設定しておくと、以後Vectorが作られるとこのプロトタイプを参照して、「自分が何をすべきか」ということを決めてくれる。たとえばこの中にadd: function (V) {this.x += V.x; this.y += V.y; },という部分があるが、これによって、Vector.prototype.addが、function (V) {this.x += V.x; this.y += V.y; }という関数になっている。これのおかげで、この後a.add(b);とやると、a.x += b.x; a.y += b.y;に対応するコードが実行される。

ここででてきたthis.というのは文字通り「これ」つまり「自分自身」を意味していて、addという関数がa.add(b);と呼び出されたならばthisはaであり、b.add(c);と呼び出されたならthisはbである。

 C++などの言語では+-*/などの「演算子」もベクトルや複素数向けに再定義できるので、a,bがベクトルならば「a +=b;」で足算ができたりする。javascriptにはそこまでの機能はないので、関数で書く。
 大事なことは、もしc=new Vector(5,3);のようにして「ベクトルc」が作られていたら、c.add(b);も同様に実行できるということ。X=new Vector(?,?);と書くだけで、Xは(Vector.prototypeを通じて)addという関数を「知っている」状態になる。

Vectorという「クラス」が何を知っているか(何ができるか)はソースを眺めてみて欲しい。上の引用部分の最後のrot90、rot270は90度と270度の回転である(a.rot90();でベクトルaが90度回転する)。