これは何?
オブジェクト指向について、なるべく簡潔に説明しています。
結論から言うと
下記はオブジェクト指向ではありません。
foo = foo + 1;
そして、下記はオブジェクト指向です。
++foo;
一体どういうことでしょうか? もう少し詳しく説明します。
非オブジェクト指向プログラミング
例えば、XY平面上の座標を次のように構造体にして表すことができます。
struct Vector
{
double x;
double y;
};
そして、struct Vector
同士の足し算を、次のような関数で定義することができます。
struct Vector Add(struct Vector foo, struct Vector bar)
{
struct Vector result;
result.x = foo.x + bar.x;
result.y = foo.y + bar.y;
return result;
}
また、ベクトルを右または左に90度回転させる関数は、次のように定義できます。
struct Vector Rotate(struct Vector foo, int rightOrLeft)
{
struct Vector result;
if (rightOrLeft)
{
result.x = foo.y;
result.y = -foo.x;
}
else
{
result.x = -foo.y;
result.y = foo.x;
}
return result;
}
上記のようなプログラミングは、オブジェクト指向ではありませんが、もう少しこのプログラムを拡張してみましょう。
先程の、struct Vector
を組み合わせて、「位置」と「方向」を持つ構造体を定義すると、下記のようになります。
struct PosAndDir
{
struct Vector position;
struct Vector direction;
};
では、この構造体を使用して書かれた次のようなプログラムは、どのように解釈することができるでしょうか。
struct PosAndDir robot = { { 0.0, 0.0 }, { 1.0, 1.0 } };
robot.position = Add(robot.position, robot.direction);
robot.direction = Rotate(robot.direction, 1);
上記のプログラムは、
- ロボットを位置
{ 0.0, 0.0 }
向き{ 1.0, 1.0 }
で初期化 - ロボットを一歩前進させる
- ロボットを右へ90度回転させる
というふうに解釈することができます。
このプログラムをわかりやすくするために、「一歩前進」と「右か左へ90度回転」を関数にしてみましょう。
下記が、一歩前進させるための関数で、
struct PosAndDir Move(struct PosAndDir foo)
{
struct PosAndDir result;
result.position = Add(foo.position, foo.direction);
result.direction = foo.direction;
return posAndDir;
}
下記が、右か左へ90度回転させる関数です。
struct PosAndDir Turn(struct PosAndDir foo, int rightOrLeft)
{
struct PosAndDir result;
result.position = foo.position;
result.direction = Rotate(foo.direction, rightOrLeft);
return result;
}
これらの関数を使うと、先程のプログラムは次のように書き直せます。
struct PosAndDir robot = { { 0.0, 0.0 }, { 1.0, 1.0 } };
robot = Move(robot);
robot = Turn(robot, 1);
しかし、これでは違和感があります。結局Move()
は一歩前進した後のstruct PosAndDir
を計算しているだけですし、Turn()
は右か左へ90度回転した後のstruct PosAndDir
を計算しているだけです。そして、計算結果をrobot
という名前の変数に代入しているのです。
このようなプログラミングは、ロボットが実際に一歩前進し回れ右している感覚からは程遠い状態です。
オブジェクト指向プログラミング
上記の拡張をより私たちの感覚に近づけるために、Move()
とTurn()
を次のように書き換えます。
ロボットを一歩前進させる関数は、下記のように書き直し、
void Move(struct Robot *foo)
{
foo->position = Add(foo->position, foo->direction);
}
ロボットを右か左に90度回転させる関数は、下記のように書き直します。
void Turn(struct Robot *foo, int rightOrLeft)
{
foo->direction = Turn(foo->direction, rightOrLeft);
}
そして、PosAndDir
構造体をRobot
に改名すると、「一歩前進後回れ右」プログラムは次のように書けます。
struct Robot robot = { { 0.0, 0.0 }, { 1.0, 1.0 } };
Move(&robot);
Turn(&robot, 1);
このように書けば、ロボットが前進して回れ右している感覚により近い状態で、プログラミングできていると言えるのではないでしょうか。
オブジェクト指向プログラミングでは、Move()
やTurn()
を使ってロボットを動かし、robot.position
やrobot.direction
は直接変更しないようにします。こうすることで、ロボットは決まった手順のみで動くので、プログラムの見通しが良くなります。
また、オブジェクト指向のMove()
やTurn()
は、動かしたいロボットの内部状態を直接変更しているのもポイントです。非オブジェクト指向のMove()
やTurn()
は、動いた後のロボットを戻り値として返し、動く前のロボットに上書きしていました。後者のプログラムは、ロボットを動かすたびにロボットを作り直しているようなもので、効率が悪いです。
このプログラムのrobot
のように、「位置」や「向き」の内部状態を持ち、「一歩前進」や「向きを変える」という振る舞いを持つものをオブジェクトといい、オブジェクトの振る舞いによって処理が進行していくようにプログラミングすることを、オブジェクト指向プログラミングといいます。
余談
オブジェクト指向は、大規模なプログラムを難なく設計するための基本的な考え方の一つです。そうです、あくまでも考え方のひとつなのです。
ですから、見通しの良いプログラムを設計するために、必ずオブジェクト指向を導入しなければいけないわけではありませんし、オブジェクト指向でプログラミングするからと言って、必ず見通しの良いプログラムが完成するとも限りません。
要は、使いようです。