![]() ![]() |
継承は、単なるモジュールの組み合わせ<combination>や有能な機構ではありません。また、継承は、多態<polymorphism>として知られる特性である多様な形態のオブジェクトへ実行時にアタッチすることもできる、柔軟な実体定義を可能にします。
この顕著なファシリティは、静的型付けと調和されなければなりません。この言語のやり方は、単純です。a := b という書式の割り当ては、もし a と b が同じ型であるならば、しかしより一般的には、もし a と b は、 B が A の子孫であるといったクラス A と B に基づく参照型 A と B であるならば、そのときにだけ許されます。
このことは、より特殊化した値がより特殊化していない型の実体へ -- しかし、その逆は不可 -- 割り当てられてもよい、という直感的な考えに対応します(たとえとして、次のことを考えてください。つまり、もしあなたが野菜を要求しているならば、緑色野菜を手に入れることは良いことですが、もしあなたが緑色野菜を求めているならば、単に "野菜" とラベルの付いた皿、あえていえばニンジンを含むこともできるようなもの、を受け取ることは、受け入れがたい、ということです)。
この能力を特に強力にさせるものは、補助的なファシリティ、つまり特徴の再定義です。クラスは、その親から継承する特徴の、いくつかあるいはすべてを再定義することもできます。属性または関数について、再定義は、元々のものを子孫によって置き換えることで、型に影響を与えることもあります。また、ルーチンについて、再定義は、元々のルーチンの本体を新たな本体によって置き換えることで、実装に影響を与えることもあります。
たとえば、多角形を記述するクラス POLYGON があるとします。その特徴は、角を表す座標の配列と、隣り合った頂点間の直線距離を合計することによって多角形の外周<perimeter>を計算する関数 perimeter とを含むとします。POLYGON の継承者は、次のように始めることもできます。
ここで、長方形のための perimeter を再定義することは、より単純でより効果的なアルゴリズムがあるときには、ふさわしいことです。明示的な redefine という副句(これは、もし rename 句があるなら、その後に来ることになる)に注意してください。
POLYGON の他の子孫はまた、perimeter の子孫自体の再定義を持つこともできます。どんな呼出しにも使用するバージョンは、その対象の実行時の形態によって判定されます。次のクラスの断片を考えてください。
多態的な割り当てである p := r は、上記の規則が理由で、有効です。もし条件 c が偽であるならば、p は、p.perimeter の計算のための型 POLYGON のオブジェクトへアタッチされることになり、それゆえ、その計算は、多角形のアルゴリズムを使用することになります。しかしながら、反対の場合に、p は、長方形にアタッチされるでしょう。それから、計算は、RECTANGLE のために再定義されたバージョンを使用することになります。このことは、動的束縛<dynamic binding>として知られています。
動的束縛は、高度な柔軟性を提供します。クライアントにとっての利点は、(外周の計算といった)操作を、その変種の一つを明示的に選択せずに、要求する能力です。この選択は、実行時にだけ発生します。このことは、多くの変種が利用されるかもしれない大規模なシステムにおいては本質的であり、それぞれの構成部品は、他の構成部品における変更に対して保護されなければなりません。
この技法が特に魅力的なのは、伝統的なアプローチにおける、最も近い同等のものと比べるときです。Pascal または Ada においては、あなたは、変種の構成部品を持つレコードと、変種間で区別するための case 命令を必要としたでしょう。このことは、あらゆるクライアントがあらゆる可能な場合<case>について知っていなければならず、どんな拡張も既存のソフトウェアの大きな本体を無効化するかもしれない、ということを意味しています。
これらの技法は、あらゆるモジュールが公開され増分的<incremental>であるという開発モードを支援します。あなたは、既存のクラスを再利用したいと思い、そのクラスに新たな文脈<context>を採用する必要があるとき、いつも、(新たな特徴、再定義された特徴、あるいはその両方を用いて)元々の特徴へどんな変更もせずに、そのクラスの新たな子孫を定義できます。このファシリティは、ソフトウェア開発、つまり不変的で増分的である活動 -- 設計によるかあるいは事態によるかどうか --、において非常に重要なものです。
多態と動的束縛の威力は、任意の制御を要求します。最初に、特徴の再定義は、上記で見たように、明示的です。第二に、言語は型付けられているので、コンパイラは、特徴のアプリケーション a.f が正しいかどうかを静的に検査できます。対照的に、動的に型付けられたオブジェクト指向言語は、実行時まで検知を延期し、最善を望んでいます。もしオブジェクトが他のオブジェクトへ "メッセージを送る"(いうならば、そのルーチンの一つを呼ぶ)ならば、まさに、対応するクラス、あるいはその祖先の一つがふさわしいルーチンを含むこともあるだろう、ということを予期しています。もしそうでないならば、実行時エラーが発生することになります。このようなエラーは、型検査された Eiffel システムの実行中には起きないでしょう。
別の言葉で言えば、言語は、動的 束縛 を静的 型付け に調和させているのです。動的束縛は、ルーチンの一つ以上のバージョンが適用可能であるときはいつでも、 正当な バージョン(対象オブジェクトへ最も直接的に適用されたもの)が選択されるであろうことを保証します。静的型付けは、そのようなバージョンが 少なくとも一つ はあることをコンパイラが保証する、という意味があります。
この方針はまた、重要なパフォーマンス上の利益をもたらします。動的型付けに必要とされる、高くつく実行時の検索とは対照的に(要求されたルーチンは対象オブジェクトのクラス内で定義してはならないが、おそらく隔たった祖先から継承されるので)、EiffelBenchの実装は、つねに、ふさわしいルーチンをコンスタントな束縛の時に見つけ出します。
表明は、再定義の威力を制御するためのさらなる機構を提供します。特有な予防措置がないと、再定義は、危険であるかもしれません。クライアントは、 p.perimeter の評価がいくつかの場合に返らないであろうということ、いわばエリア、をどのように保証されうるのでしょう? 事前条件と事後条件は、最後の再定義者へ譲渡された、結局自由を制限することによって答えを提供します。この規則は、どんな再定義されたバージョンも、より弱い、あるいは等価の事前条件を満足し、元々の事後条件よりも強力でしかも等価な事後条件を保証しなければならない、ということです。言い換えると、それは、元々の表明によって設定された意味的な境界の中にとどまらなければならない、ということです。
再定義と表明に関するこの定義は、「契約による設計」理論の一部であり、再定義と動的束縛は部分契約<subcontracting>を導入します。たとえば、POLYGON は、実行時に長方形オブジェクトへアタッチされているどんな実体へも適用されるとき、perimeter の実装を RECTANGLE へ部分契約させます。正直な部分契約者は、主契約者によって受け入れられた契約を守るように束縛されます。このことは、次の二つのことを意味しています。一つは、それはクライアント上のより強い要件を取り込んではいけないが、より一般的な要求を受け入れてもよいので、事前条件はより弱くなるかもしれない、ということです。そしてもう一つは、少なくとも主契約者によって約束されたのと同じくらい達成されなければならないが、それ以上を達成してもよいので、事後条件はより強くなるかもしれない、ということです。
![]()
URL
for this page: http://www.eiffel.com/doc/manuals/language/intro/polymorphism.maker.html.
Copyright 1994-1998 Interactive Software Engineering Inc. All rights reserved.