本当に初心者の人に捧げるコンピューター入門


補足その8−A

オブジェクト指向って?

 本文でちょっとオブジェクト指向プログラミングのことについて触れました。ここではそれについて簡単に説明したいと思います。
 当然このことについては何冊も本が出ていたりするぐらいなので、あまり詳細に書くわけには行きません。しかし基本の部分は簡単です。

 オブジェクト指向とはプログラムを部品化するための思想であり、それを実現するための技術のことです。
 そしてオブジェクトとはこの部品のことであると思っておけば大筋は合っています。

 ・・・と書いたところで、多分全くピンとこないでしょう。実際この有り難みを実感するためには、プログラムを、しかもかなり大きなプログラムを作らないとだめです。

 なぜかって?
 皆さんも図書館の分類方法は知っているでしょう?日本十進分類とかいう奴です。あれがないと図書館に行って目的の本を探し出すなんて、ほとんど不可能です。
 しかし自分の家の本棚をあれで分類している人はまずいないはずです。なぜならそんなにたくさん本があるわけではないし、その程度の量でそんな分類をしてたら、分類作業の方に手間がかかってしまうでしょう。

 どうしてオブジェクト指向が小さいプログラムではあまり効果がないのかというと、大体こんな感じと思ってもらえばいいでしょう。


プログラムの部品って?



 さてそれでは、プログラムの部品という概念ですが・・・実は本文の説明にも立派な部品が登場しています。すなわち関数とかサブルーチンです。本文でもあれを使うとプログラムがかなり書きやすくなり、分かりやすくなりました。
 だったらオブジェクトって何なんでしょう?

 例えばコンピューターも様々な部品の集まりです。最近DOS/Vマシンの自作が流行っていますね。もしかしたらそういう記事を見たことがあるかも知れません。
 そこでは、筐体やマザーボード、グラフィックボード、ハードディスク・・・などの「部品」を買ってきて組み立てるやり方が書いてます。ですから自作と言っても、ほとんどレゴブロックを組み立てるようなもので、手順さえ間違えなければ誰でもできる作業です。

 しかしそれではマザーボードを自作しようとしたらどうなるでしょう?それに必要な基盤やICチップ、ダイオード、コンデンサ、抵抗・・・といった「部品」は探せば見つかりますが、それらの部品からちゃんと動作するマザーボードを作り上げるのは大変な作業です。

 例えて言えばオブジェクトとはマザーボード・グラフィックボートといったレベルの部品であり、関数やサブルーチンとはLSIレベルの部品なのです。


オブジェクトの正体



 それではこのオブジェクトとはコンピューター上ではどう表されているのでしょうか?

 オブジェクトとはの一種で、それは高級言語のところで書いたレコード型の発展型のような物です。
 レコード型はいくつかの変数をひとまとめに表す方法でした。しかし「オブジェクト」の場合は、変数だけでなく、同時に関数やサブルーチンも中に含めることができます。
 以下ちょっとしたオブジェクトの定義を書いてみます。

 {TMyLadiesというオブジェクト定義}
 type TMyLadies = class(TObject)
 private
   Ladies:array[1..1000] of GirlData;     {GirlData型変数の配列}
   DataCount:integer;                      {現在のデータ数を入れてお
                                            く変数}
 public
   constructor Create;                     {後で説明}
   destructor Destroy:override;            {後で説明}
   function ReadData(n:integer):GirlData;  {Ladiesの第n項目目のデータ
                                            を返す関数}
   procedure AddData(Lady:GirlData);       {新しく項目を追加するサブ
                                            ルーチン}
   procedure RemoveData(n:integer);virtual;{第n項目を削除するサブル
                                            ーチン}
   function LadyCount:integer;             {現在の項目数を返す関数}
 end;
 上ではTMyLadiesという名前のオブジェクトを定義しています。
 見た感じはレコード型の定義方法と似てますね。変数宣言や関数・サブルーチンの定義は、本文に書いてあったものそのままです(それ以外のprivateとかpublicとかoverrideとかvirtualといった呪文はこの際気にしないでください・・・ええ?気になる?じゃあ
こっち

  • もちろんオブジェクトの中の関数やサブルーチンは、どこか別な所に中身を書いておかなければなりません。その場合にはその関数やサブルーチンが何のオブジェクトの中のルーチンかをはっきりさせるために、下記の例のように関数名にオブジェクト名をくっつけておかなければなりません。
  
 procedure TMyLadies.RemoveData(n:integer);
 begin
   {データを削除するための処理が書かれている} 
 end;

 これは言ってしまえばもっとややこしい物に名前を付けるの発展版です。

 それではこのオブジェクトはどうやって使うのでしょうか?
 オブジェクトがのようなものであることを思い出せば簡単ですね。すなわちこの型の変数を宣言すればいいわけです。
 すなわち上のTMyLadiesを使うためにはPascalでは、

 var Ladies1:TMyLadies;
     Ladies2:TMyLadies;
とか書いて、その型の変数を作ります。もちろんいくつでも作ることができます。
 オブジェクトに含まれる関数やサブルーチンを使用するためには、当然どの変数の中のルーチンを使用するか指定しなければいけないので、

 n:=Ladies1.LadyCount;
とかいった使い方をします。
 レコード型に含まれる変数を参照するときと同じですね。

 当然この関数は自分自身の中にある変数を元に計算します。従って、上のように書けば関数はLadies1のデータの中にある項目の数を数えてくれるわけです。


こうするとどういういいことが?



 とまあ、オブジェクトとはこういう物なのですが、こうすると一体何がいいのでしょう?
 実際、別にこうしなくても、データをレコード型にして、それを扱う関数群を作っても同じようなことができます。
 例えば、

 {使用するデータをひとまとめにレコード型にしておく}
 type LadiesData=rocord
     Data:array[1..1000] of GirlData;
     DataCount:integer;
 end;
 {それを扱うための関数群}
 function ReadData(var Ladys:LadiesData;n:integer):GirlData;
 procedure AddData(var Ladys:LadiesData;Lady:GirlData);
 procedure RemoveData(var Ladys:LadiesData;n:integer);
 function LadyCount(var Ladys:LadiesData):integer;
 とかしておけば、データの数を数えたいときは

 var Ladies1:LadiesData;
     Ladies2:LadiesData;

 n:=LadyCount(Ladies1);
 と書くだけで済みます。手間としては大差ないですね。
 それだったらどうしてわざわざオブジェクトという物を導入したいのでしょう?

 第一の理由は、ひとまとめになっているので管理が楽だという点があります。
 オブジェクトにしておくと、LadiesDataというデータとそれに対する操作がひとまとまりになっています。それに対して、上のやり方だとデータと操作は別々になっています。
 ReadDataという関数がLadiesDataを扱うということは、変数の中にLadiesdata型の変数があるから何となく想像は付きますが、これがどこか別な場所に書いてあったら(そうすることも可能です)気が付かないかも知れません。
 そしてもしちょっと仕様が変わったとかで(例えば1000人では多すぎるので100人に変更したとか・・・)書き換えが必要になったとして、関数がバラバラにあったらどれかうっかり忘れてしまうかも知れません。

 ところがオブジェクトにしておくと、必要な関数はすべてオブジェクトの定義の中に書いてあります。従ってうっかり修正し忘れるといったことが少なくなります。
 すなわち、別々な書き方をすると、関数やサブルーチンを見れば、それがどういうデータを使用するかは大体分かりますが、データを見ただけではそれがどういう関数やサブルーチンで利用されるか分かりません。

 しかしデータの構造が変更されるというようなことは良くあります。そういう場合に関連したルーチンを間違いなく探し出して修正するのは、プログラムが大きくなればなるほど大変な作業になります。
 しかし、データとそれに対する操作がひとまとまりに定義できると、そういう作業の際にも間違いが減ります。

 第二の理由はブラックボックス化がやりやすいことです。
 皆さんの家にも大抵ビデオとかラジカセなんかがあるでしょう。どれか一つの使い方を覚えておけば、全然違うメーカーの物を買ってきても操作にはあまり困りません。しかし当然メーカーが違えばその中身は異なっています。
 このように中身は違っていても使い方を同じにしておけば、使う方は全然困りません。ブラックボックス化とは、そういう風にすることです。

 上のオブジェクトの定義の中に、privateとかpublicといった呪文がありました。これは実はこのブラックボックス化するための機能なのです。
 privateと書かれている部分は、そのオブジェクト内部の関数またはサブルーチンからしか使えません。上記の例であれば、オブジェクトの宣言の中に書かれている6つの関数・サブルーチンの中でしか使えないのです。
 それに対してpublicと書いてある部分にある関数・サブルーチン・変数は、どこからでも使用できます。
 例えば上の例だと

 n:=Ladies1.LadiesCount;            {publicの中のルーチンを呼ぶ} 
というのは問題ありませんが、

 n:=Ladies1.DataCount;              {privateの中の変数を読む}
とやったらエラーが出ます。

 どうしてこんなことをするのでしょう?
 それはとにかくpublicの中の関数や変数の仕様さえ変わらなければ、privateの中はどうせ外から絶対アクセスできないのでどう変更を加えようと勝手なのです。

 例えばあなたの作ったオブジェクトを既に別な人がばりばり使っていたとします。諸般の理由であなたはそのオブジェクトを変更しなければならなくなりました。その際にも、publicの中の関数やサブルーチン、変数の仕様さえ変えなければ、ほとんど好き勝手に中を書き換えることができるわけです。
 使用している側もそれで全く困ることはありません。

 ところがこれをバラバラに作っているとどうなるでしょう?多分使っている側のプログラムにも何らかの変更が必要になることでしょう。

<次に続く>



前へ 目次へ 次へ