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


補足その8−B

オブジェクト指向って? の続き

 と、ここまで書いた内容だけがオブジェクトの全てであったら、はっきり言ってあまり大したことはないです。
 データと操作をひとまとめにしておくとか、ブラックボックス化するだけなら、オブジェクトがなくとも何とかなります。もちろんオブジェクトにすると必然的にそうなるのに対して、意識的にそうしなければならないのは大きいことです。人間は分かっていてもついついというのは良くあることです。

 しかしオブジェクト化することで、もうひとつとてつもない大技ができるようになるのです。それがこの項のタイトルでもあるオブジェクトの派生です。

オブジェクトの派生



 これがどういうことかというと、あるオブジェクトをベースにして新しいオブジェクトを作ることができるということです。

 例えば前の例のTMyLadiesというオブジェクトに、新しい機能、例えば名前からデータの番号を逆引きしたいとかいったものを追加する必要があったとします。

 もちろん元のTMyLadiesを書き換えてもいいのですが、もうTMyLadiesは他人も使っていて、勝手に書き換えてはいけないことになっています。
 そういう場合どうしたらいいのでしょう?いちいち使っている人の所に行って、書き換えてもいいかどうか尋ねるのでしょうか?やってられませんね。

 しかしオブジェクトの派生という技を使えば、そういうことは全く気にする必要はありません。

例えば以下のように書きます。

 type TMyLadiesNew = class(TMyLadies); 
 こうするとTMyLadiesNewという新しいオブジェクトができます。一見中身は空っぽのようですが実は、それはTMyLadiesと全く同じ動作をします。名前だけが違ったコピーのようなものです。
 そして

 type TMyLadiesNew = class(TMyLadies)
 public
     function SearchData(Name:string):integer;  {逆引き用の関数}
 end;
とかして、必要な追加部分だけを書きます。
 以上です。

 var Ladies:TMyLadiesNew;        {←今回定義した新しいオブジェクト}
と変数宣言すると、当然

 n:=Ladies.SearchData('YASUE');    {今回追加した機能}
というように追加した関数が使えます。またベースになっているTMyLadiesにある機能の

 n:=Ladies.LadiesCount;            {ベースのオブジェクトにある機能}
も問題なく使えます。
 当然ですがもし

 var Ladies:TMyLadies;           {←ベースの方のオブジェクト}
と定義してあった場合は

 n:=Ladies.SearchData('YASUE');   {ベースにはSerchDataは入ってません}
と書いたりしたらエラーになります。


オブジェクトの修正



 また新しい機能を追加するだけではありません。既存の機能を修正することもできます。
 例えば以下のようなオブジェクトを作ったとします。

 type TMyLadiesNew = class(TMyLadies)
 public
     procedure RemoveData(n:integer);override;
     function SearchData(Name:string):integer;
 end;
 よく見るとRemoveDataというのは元のTMyLadiesにもあるサブルーチンです。ここで定義し直したら、同じ名前のサブルーチンが二つあることになります。こういう場合どちらのサブルーチンが実行されるのでしょう?
 それは当然新しく定義された方です。すなわち、TMyLadiesNewにある方のRemoveDataが実行されます。これはすなわちRemoveDataという機能を書き換えたことに他なりません。

 またもっと良くあるのは、現在の機能を拡張したいような場合です。
 例えばRemoveDataする際にそのデータのDone変数がFalseの場合に「本当にあきらめますか」とかいったダイアログボックスを出したいとかです。

 そのプログラムを書く場合、データを削除するところは、TMyLadiesの機能と全く同じです。同じことを二度も三度も書くのは面倒な上に、修正があった場合は何カ所も同時に修正しなければなりません。
 こういったときベースになっているオブジェクトの同名のルーチンを実行するという機能があると便利です。そして実際にそういう機能もあります。

 procedure TMyLadiesNew.RemoveData(n:integer);
 begin
     if Data[n].Done=False then MessageBox('本当にあきらめますか');
     Inherited;      {ベース(TMyLadies)のRemoveDataを実行する}
 end;
 上記のInheritedと書かれている行で、ベースになっているTMyLadiesの方のRemoveDataが実行されます。

 こうするとベースになっているオブジェクトに何ら手を加えることなく、機能修正版を作ることができるのです。

 これは便利だとは思いませんか?
 そしてこういう機能を実現するためにはデータが上記のようにワンセットになっている必要があるのです。たとえはInheritedの機能を使うためには、上位オブジェクトの関数がどこにあるか分かっていなければなりません。しかし、それをバラバラに書いていたら、探し出すのはほとんど不可能になってしまいます。

 オブジェクト化していれば、その関数は必ずベースになっているオブジェクトの中にあることが保証されます。ベースとなっているオブジェクトの場所さえ分かっていれば問題ないわけです。


オブジェクト指向プログラミング



 というわけで、オブジェクト指向プログラムでは大体以下のような感じで全体を作っていくことになります。

(1)まず最も基本となる物を作る
 例えば「コントロール」とかいうオブジェクトを作ります。
 これはスクリーン上に四角いエリアを確保するだけのものです。

(2)基本となるオブジェクトから派生したオブジェクトに機能を追加する
 例えば上の「コントロール」をベースにして、新しくオブジェクトを派生して、それを文字入力できるように改造します。それを「エディットボックス」とか名付けておきます
 また四角いエリアにタイトルバーなんかをつけて、手でサイズ変更できるようにしたものも作って「フォーム」とか名前を付けておきます。

(3)オブジェクトを組み合わせて全体を作る。
 フォーム上に、エディットボックスを張り付けたら、エディタの出来上がりです・・・

 手順はまあ分かりやすいといっても、これを最初から全てを自分で作っていたらやっぱり大変です。

 しかしプログラム言語を買うと、必ずライブラリというのが付いています。それは汎用的に使える関数やサブルーチン集のことです。オブジェクト指向言語では、当然汎用的に使えるオブジェクト集というのがおまけに付いてきます。

 そういう物があったらどうでしょう?プログラマーは上の3ステップ目だけをすればいいわけです。だとしたら楽ですね。
(場合によっては2もしなければいけないかもしれませんが・・・)

 ちょっと下の図を見てください。これはDelphiというPascal言語に付属している、ビジュアルコンポーネントライブラリ(VCL)のHELPの一部です。


 その中には、上の例であげたようなオブジェクトが既に含まれています(色の赤い奴です)ということは、このライブラリを使えば、プログラマーはそれを組み合わせるところだけをすればいいわけです。

 ところで図を見るときれいに階層分けされていますね。どうしてでしょう?
 って、考えてみたら当たり前です。基本になるオブジェクトから新しいオブジェクトを派生して、機能を追加して・・・という作り方をしていたら、どうやったってこういうきれいな階層になってしまうわけです。


というわけで



 非常におおざっぱですが、まあオブジェクト指向というのはこんな感じのものです。
 読んでいただければ分かると思いますが「おお!こうなっていればもう誰だってプログラムが作れるぜ!」と思った人はいないでしょう。やっぱりなんだか難しいです。

 またオブジェクト指向では、順番に積み木を積み重ねていくような作り方をしていきます。その場合に土台の方に問題が出たらどうなるのでしょうか?

 例えば、あなたがウインドウズ上で時計を作ったとします。それを人に見せたら
「ああ?時計は丸いに決まってるだろう!」
とか言われてしまいました。では丸いウインドウを作るには??

 ところがいろいろな物の基礎となっている「コントロール」というオブジェクトは、四角いことを前提としてできています。こうなると一見何でもなさそうな修正に見えますが、実はこれは大変な騒ぎになります。

 上記の説明で、オブジェクト指向ではベースになっているオブジェクトの修正が楽にできると書きましたが、これは実はどんな修正でも楽にできるわけではありません。基礎的な部分に問題が発生した場合、その修正がオブジェクト内部だけでは済まなかったということもあり得ます。そうなったら結局、そのオブジェクトをベースとしている全てのオブジェクトを修正する・・・といった大作業になってしまうこともあり得るのです。

 オブジェクト指向プログラミングでは、部品を組み合わせて作れる物であれば、非常に楽に作ることができます。しかし必要な部品がなかった場合、これは結局今まで同じように一生懸命その部品を作るしかないわけです。

 そして部品を作る際には注意が必要です。それは、まず部品として使いやすいことです。そしてなにより、何か問題が発生したとしても、オブジェクト内部の修正だけで済むように、あらかじめよく考えて設計しておかなければならないということです。

「はあ?だって未来にどんな問題が起こるかなんてわかんないだろう?」

と思われるでしょうね。でも・・・

「だからどんな問題が起こってもいいように設計しておけ!」
「だって僕は超能力者じゃないんですよう!」
「ぐたぐた抜かすな!でないとクビだ!」
「しくしく」

というのが現状なのです。

 高級言語とは、機械語のような非人間的な物を、可能な限り人間的な物に変えていこうとする試みです。オブジェクト指向はまた一歩野望に近づいてはいますが、野望の達成はまだまだ先のことなのです。


細かい嘘のフォロー



 オブジェクト指向についてはだいたいこんなものですが、上記の説明には話を簡単にするために結構手抜きをしています。以下はそのフォローです。

◆クラス
 オブジェクト指向言語では、このオブジェクトのことを通常クラスといいます。
 
定義の所classと書いてあるのはそういうことです。実はオブジェクトとクラスは微妙な違いがありますが、まあ皆さんの場合はあまり気にしなくてもいいでしょう。

◆コンストラクタとデストラクタ
 説明ではオブジェクトの変数を定義すればそのまま使えるように書いていましたが、これは実は嘘です。
 普通の変数ですと、varで宣言してすぐ使えるのですが、なにぶんオブジェクトは複雑なので、以下のように使うためには宣言した変数に実際の中身を入れてやらねばなりません。
 オブジェクトの定義の中にconstructordestructorというのがありました。これはサブルーチンの一種ですが、オブジェクト型の変数を実際に作るとき破棄するとき専用に使います。
 ですからオブジェクトを実際に使用したい場合は、変数宣言の後に

 Ladies:=TMyLadies.Create;       {constructorを実行する}
 このことをオブジェクトのインスタンスを作ると言ったりします。
 同様に要らなくなったら

 Ladies.Destroy;                 {destructorを実行する}
とします。こうすればメモリー中からそのオブジェクトが消えてなくなります。

◆仮想メソッドとオーバーライド
 説明では、オブジェクト中のルーチンを拡張したいときに、Inheritedというのを使って、ベースになるオブジェクトの同名のルーチンを呼び出していました。

 実はいつでもこうできるわけではありません。そのためには、ベースになっているルーチンにこのルーチンは派生されたオブジェクトから呼び出されるかも知れないよマークを付けておく必要があります。
 定義の中にこっそりとvirtualとかが付け加わっていたのは、このマークです。そしてこのマークが付いている関数やサブルーチンを仮想メソッドと呼んだりします。

 同様に新しく定義し直された方にはoverrideというマークを付けておかないと、Inheritedの機能が使えません。

 これが何を意味するかというと、機能修正をするためには、あらかじめこの関数やサブルーチンは機能修正される可能性がある、ということを予測しておかないといけないわけです。

 だったら最初から全部にvirtualって書いておけばいいって?そういうことをすると、今度はプログラムのパフォーマンスが低下してしまいます。

◆プロテクト
 オブジェクト定義の中に、privateとかpablicとか言う呪文がありました。
 その他にこれに類する呪文に、protectedというのがあります。
 これは、ここに書かれている変数や関数・サブルーチンは、派生オブジェクトからは使えるが、関係ないオブジェクトや通常のルーチンからは使えないという意味です。



前へ 目次へ 次へ