[ Eiffel Liberty | GUERL | sOOap ]
by
Alan A. Snyder
and
Brian N. Vetter
5 - 継承と特徴の適応 5.0 はじめに 5.1 選択 5.2 名前変更 5.3 再定義 5.3.1 特徴の用法表示 5.4 継承された特徴の公開状態の変更 5.5 定義取消 5.5.1 定義取消の意味的制約 5.5.1.1 凍結された特徴
おそらく、ソフトウェアの再利用性と適応性への最も有力な貢献は、継承の考え<notion>です。この強力なファシリティを用いて、ソフトウェアの構成要素は、以前に書かれ、そしてより重要なことは、以前に試験されたコードを適応するための単純な仕事になります。オブジェクト パラダイムのこの重要な特徴が、オブジェクト指向であると主張する大部分のプログラミング言語においてほとんど完全には認められないということは、ほとんど逆説的なことです。あるものは単一継承の機構を提供し、別のものは多重継承を提供しますが、このような曖昧さを名前の衝突<collision>として効果的に扱うためのファシリティ、そして反復された継承、を提供していません。
一方、Eiffel は、これらの限定において、とてもユニークです。この言語は、多重継承のすべての恩恵をそれらの最も完全な結果へ使用されることを可能にするために、いくつかの機構を提供します。Eiffel は、子孫クラスに、仮想的にすべての継承された特徴について、名前を変更し、定義を取消し、選択し、実効化し(実装を延期<defered>された特徴の場合)、そして変更することを許します。もちろん、曖昧なやり方で、あるいはおそらく安全でないやり方で、継承された特徴を適合させようと意図しているときには、新しいクラスに置かれた意味的な限定があります。
特徴の適応についての膨大な組み合わせへの私たちの旅を始めるために、私たちは、選択の機構を見ることにします。少なくとも二つのクラス、ここでは A と B、から継承する所与のクラス C について、A と B は、両方とも、実例に対して f と名付けられた特徴を持つ可能性が存在します。もし C が A と B から継承しているならば、私たちは、名前の衝突<collision>とか衝突<crush>と呼ばれるものを持ちます。この曖昧さの問題は、この例において、より顕著に示すこともできます。
class A feature f : INTEGER; end -- A class B feature f:: REAL; end -- B class C inherit A; B; -- 違法 ! end end -- Cそれで、質問は、A と< B>B から継承した、f という同じ名前を持った二つの特徴をどのように解決するか、ということです。select(選択)は、一つの解決策を提供します。それは、次のように C のテキストを修正することによります。
class C inherit A select f; B end; feature ... end -- C私たちは、f を A から継承することを許したいと思っているが、B からではない、と明示的に記して、衝突を解決します。B からの特徴 f は、次のように、もし select 句を B の inherit 句に置くべきであったならば、クラス C では継承されません。
class C inherit A; B select f; end; feature ... end -- C反対のことは真であったでしょう。つまり、f は、B から継承したでしょうが、A からではなかったでしょう。
Select 句無しでは、実行時環境が C の中の f を動的に束縛するが A のバージョンへなのか、B のバージョンへなのかを告げることはできなかったでしょう。コンパイラは、もしこの曖昧さを認めないならば、このような継承の状況は言語上の違反であるとプログラマへ報告しなければなりません。明らかに、このやり方で継承する理由は、とても良くあるかもしれません。すなわち、単純な名前付けの重複は、意図している継承から一つを妨げてはいけません。
何らかの文脈では、子孫のクラスが、二つの異なるクラス Aと B から f と名付けられた二つの特徴を区別して継承することを望むこともあります。この場合に、Aと B のいずれかから一つだけ選択することは、f の両方のバージョンを獲得することをまったく可能にしないことになります。選択は、f のただ一つのバージョンに親のどの集合から継承されることを可能にする、ということを思い出してください。
しかしながら、私たちは、たとえば、f の一つのバージョンを g へ名前を変更でき、このようにもし名前の衝突があればそれを解決し、f の二つのバージョンに一方を異なる名前にして獲得することを可能にします。クラス C の次の改訂は、この状況を記述します。
class C inherit A rename f as g; B; end; feature ... end -- Cこの状況では、f の A のバージョンは、g へ名前を変更しています。このことは、二重の目的があります。一つ目は、A と B からの f の二つのバージョンの名前の衝突を解決することです。二つ目は、f の両方のバージョンを C において継承することを、プログラマへより偉大な制御を与えることになる、異なる名前での A のバージョンを許すことです。
同じく、名前の衝突が無くても、このファシリティを使用できます。実例として、ある特徴が、もしその新たな周囲の環境に一致するのに十分記述的でないならば、そのシステムにおける新たな役割を反映させるために、名前を変更することもできます。
class EMPLOYEE inherit PERSON rename occupation as company_division end; feature ... end -- PERSON上記の文脈で、私たちは EMPLOYEE の occupation が EMPLOYEE が関係している会社のために働いていることを知っているから、occupation を company_division として名前を変更し、その意味論のすべてを完全なままに維持することが可能です。
これは、occupation を参照する PERSON から継承した他の特徴の有効性という質問の一つを持ち出します。つまり、もし PERSON からの特徴がoccupation の項目に依存するならば、それは、occupation はもはや存在しないから、いまだに有効な特徴でしょうか? 次のことを、考えてください。
class A feature x, y : INTEGER; set_x (new : INTEGER) is do x := new end; end -- A class C inherit A rename x as x1 end end -- Cいま、もし私たちがクラス C の実例を生成し、set_x というルーチンを呼び出そうと意図しているならば、これは合法的でしょうか? 答えは、「はい」であり、これは完璧に合法的です。X が x1 へ名前を変更されているという事実は、ルーチン set_x に影響を与えません(set_x はそれ自体プログラマの部分に関する一貫性のために set_x1 へ名前変更されているはずである、と述べられているはずであるけれども。コンパイラは、すべての参照を名前変更された特徴へ名前を変更するようにあなたに要求しません)。けれども、もしもう一つのルーチンが x1 の値を double にする double_x1 は C の文脈で書かかれるべきであると言うならば、x ではなく x1 へ参照されなければなりません。同じく、X を x1 へ名前を変更する効果は、識別子 x を C へ利用可能とします。つまり、C 自体は、A からのもともとの x とはまったく異なる特徴 x を宣言することもできます。
このように、私たちは、特徴を新たな環境へ適応するために特徴を選択し名前変更するやり方を、ずっと見てきました。Eiffel は、特徴をその新しい環境へ適応するために、もう一つの機構を提供します。
ソフトウェア開発において共通して遭遇する場面は、ある特徴が別の特徴の名前と必ずしも競合しないときですが、アルゴリズム上の改善またはこのような性質の他の利益を反映させるために、特徴を拡張または特殊化するためにだけ適しているかもしれません。このことは、再定義が登場する場面です。継承される特徴を再定義することは、特徴をその新たな環境へ正しく適応することを許します。ソフトウェアのダーウィニズム(進化論)のこの形式は、オブジェクトパラダイムの最も魅力的な能力の一つです。
私たちは再定義の実際の例を与える前に、選択と名前変更の句が取り込まなかった再定義の特徴の上に置かれた考察しうる特徴がある、ということを言うべきです。この制約は、特徴 f の新たな用法標示<signature>がその元々の用法標示へ適合しなければならない、ということです。いま、特徴の用法標示の正確な意味付けを議論するのがふさわしいと思えます。
特徴 f の用法表示<signature>は (A,R) という対であり、ここで、A はルーチンまたは関数の引数の型の順序(実体に対しては空)であり、R (ルーチンに対しては空)は、関数のための返り型であるか、あるいは実体に対してはまさしく実体の型です。次の例は、このことをより明らかにさせます。
特徴<feature> 用法標示<signature> ----------------------------------------- ------------------------ 変数の属性(実体) x : INTEGER (<>,) y : REAL (<>, ) 引数無しの関数 day_of_year : INTEGER (<>, ) 引数付きの関数 func(x : INTEGER; r : REAL) : REAL ( , ) func(a : SOME_CLASS; x : INTEGER) ( ,CLASS) 引数付きの手続き proc(a : CLASS; r : REAL; x : INTEGER) ( ,<>) proc(x, y : INTEGER) ( , <>) ----------------------------------------- ------------------------- 特徴の用法標示は引数があればそのすべての型の順序とその返り型(関数の場合に)である、あるいは、実体の場合にはまさしくその属性の型である、と単純に考えてください。
このことは、再定義で何をしなければならないのでしょう? 上で言及したように、継承した特徴を再定義したいと願うクラスの上に置かれた制約があります。つまり、新たに定義した特徴の用法標示は、再定義している特徴の用法標示へ適合しなければなりません。例は、次のようになります。
class A feature set_all( x, y : INTEGER; r : REAL ) is do ... end -- set_all end -- A class B inherit A redefine set_all end; feature set_all( i, j : INTEGER; s : REAL ) is do ... end -- set_all end -- Bここで、私たちは、set_all の A のバージョンとはルーチンの本体がアルゴリズム的に異なる set_all を再定義しているクラス B の事例を持っています。 気付いていただきたいのは、次のように、B において定義された set_all の用法標示が A における set_all の用法標示のものへ適合する、ということです。
Feature Signature --------------------------------- --------------------------- set_all(x, y : INTEGER; r : REAL) (,<>) set_all(i, j : INTEGER; s : REAL) ( ,<>) --------------------------------- --------------------------- 用法標示を適合することと、用法標示を一致させることとの違いは、何でしょうか? 用法標示 Sは、もし次のことが真を保持するならば、用法標示 P へ適合すると言われます。
用法標示の適合性
- 両方の用法標示の引数の並びにある型の数は、正確に一致しなければならない。
- S の引数の並びにあるそれぞれの型は、P の引数の並びにおける対応する型の子孫である。覚えておいていただきたいのは、引数の並びにある対応する型が同一であってもよいので、型もそれ自体の子孫である、ということです。
これが意味することは、P と S の引数の並びにおける型の数と返り型が、正確に同じでなければならない、ということです。二番目の規則は、再定義された特徴はその引数と返り型において正確に同じ名前を使用することを要求されない、ということを述べています。すなわち、それが合うと思えるように、そのかたのどれかの子孫あるいはすべての子孫を使用してもよい、ということです。
上からの私たちの例へ戻って、次の内容を良く見てください。
class A feature set_all( x, y : INTEGER; r : REAL ) is do ... end -- set_all end --A class NEW_INTEGER inherit INTEGER end; -- あなたは、INTEGER から継承できます !! feature ... end --NEW_INTEGER class B inherit A redefine set_all end; feature set_all( i, j : NEW_INTEGER; s : REAL ) is do ... end -- set_all end -- Bこれは、合法的でしょうか? 絶対に! B における set_all の用法標示は実に A における set_all の用法標示へ適合しているから、これは、合法的です。NEW_INTEGER は、INTEGER を NEW_INTEGER の継承者<heir>とさせて、INTEGER の子孫として定義していました。新しい用法標示は、次のように、
Feature Signature -------------------------------- ---------------------------------- set_all(x,y:INTEGER;r:REAL) (,<>) set_all(i,j:NEW_INTEGER; s:REAL) ( ,<>) -------------------------------- ----------------------------------- 一致してはいませんが、適合しています。つまり、B の set_all は、 A の set_all へ適合しています。opposite が真であることは、明らかに不可能です。A の set_all の用法標示は、B の set_all の用法標示のものへ適合しません。
2.4節からの、私たちが唯一な特徴(あるいは特徴群)を特定のクラスまたはクラス群へ明示的に公開できた、ということを思い出してください。実例は、次です。
class VEHICLE feature {DRIVER} identification : INTEGER; end -- VEHICLE特徴 identification(身分証明)は、クラス DRIVER の子孫クライアントへだけ利用可能である、あるいはクラス DRIVER 自体である、ということを宣言します。それで、私たちが次のようにするときに、この規則へ何が起きるかといった質問が上がります。
class LAND_VEHICLE inherit VEHICLE end -- LAND_VEHICLEidentification は、より一般的なものに対して、その限定的な公開状態<export status>を失いますか(すなわち ANY クラスへも利用可能になりますか)?
私たちは、VEHICLE から継承したときに、identification の公開状態を明示的に再宣言しなかったので、この特徴の状態は同一のままです。
しかしながら、私たちは、この公開状態と、この問題のための他のどんな継承した特徴も、export 句で、明示的に変更することができました。
class LAND_VEHICLE inherit VEHICLE export {DMV_PEOPLE} identification end; end; -- LAND_VEHICLEこのことは、LAND_VEHICLE をクライアントとして使用したいと願っているどんなクラスも、もしそれが DMV_PEOPLE の継承者であるならば、identification を直接にアクセスだけができる、ということを述べています(もちろん、DMV_PEOPLE クラス群自体は、identification をアクセスすることもできます)。特徴の公開状態を再宣言したいと願っているクラスの子孫の上に置かれた限定はありません。つまり、上の LAND_VEHICLE へ参照して、DRIVER へ関連されるクラスへ新たに identification を公開するという要求はありません。特徴についての直前の公開状態は、それを修正したいと願っている子孫のクラス群の上で制約を一切取込みません。
また、私たちは、まさに DMV_PEOPLE の代わりに、identification を公開したいと願う、クラス群のグループを宣言することもできました。
class LAND_VEHICLE inherit VEHICLE export {DMV_PEOPLE, MECHANIC, FBI} identification end; end -- LAND_VEHICLEこの場合に、DMV_PEOPLE、MECHANIC、あるいは FBI は、LAND_VEHICLE のクライアントであり、identification へ非限定的なアクセス権をもちます。特徴の初期の公開状態は、その公開状態を再宣言する際に、制限を一切取込みません。私たちは、identification を NONE、あるいは ANY へ容易に公開されたように、あるいはクラス群の中のどの組み合わせも持つことができました。
構文的な容易さのために、Eiffel は、継承されたクラスのすべての特徴を一つのクラスまたはクラス群のグループへ公開することを許す機構(キーワード)を提供します。キーワード all を用いる際に、あなたは、特有のクラスからのすべての特徴が、いまや特定のクラスまたはクライアントのグループへ利用可能とされている、と宣言することもできます。
class FEATURES_A_PLENTY inherit FEATURES_GALORE export {LUCKY_CLASS} all end feature {NONE} private_data : INTEGER; end -- FEATURES_A_PLENTYFEATURES_A_PLENTY が多くの特徴を FEATURES_GALORE から継承していると、仮定してください。FEATURES_GALORE からのあらゆる特徴の公開状態は、いまや、LUCKY_CLASS とその子孫のすべてへ排他的に利用可能となっています。特徴 private_data は、FEATURES_A_PLENTY で宣言されており、FEATURES_GALORE の export 句に従属しません。どんなクライアントも、NONE へ公開されているから、private_data を直接には一切アクセスできません。
Eiffel の特徴を適応する能力のより興味深いことは、特徴の定義を取り消す能力です。つまり、inherit 句にある特徴の存在を効果的に無視することです。これは、後で探求することになる、いくつかの意味的な質問を持ち出します。ここでは、次のことを考えてください。
class NEW_CLASS inherit OLD_CLASS undefine redundant_routine; end end -- NEW_CLASSOLD_CLASS から継承していたであろう redundant_routine は、もはや NEW_CLASS には存在しません。それを理解することによって、私たちはまた、その識別子を開放します。つまり、私たちは、もしお望みなら、NEW_CLASS において特徴 redundant_routine を宣言することもできます。
特徴の定義を取り消すことは、定義を取り消した特徴と他の特徴との間の依存性と(知らず知らずに)インターフェイスすることになります。次の内容をよく見てください。
class DEPENDENT feature small_routine( n : INTEGER ) is do ... end; -- small_routine big_routine( arguments... ) is do ... small_routine( x ); --call small_routine ... end -- big_routine end -- DEPENDENTこのクラスは、その構成においては、完璧に通常であるように思え、良く使用するクラスであるかもしれません。次のように、もし私たちが新たな子孫のクラスにおいて small_routine の定義を取り消そうと試みるならば、
class NEW_DEPENDENT inherit DEPENDENT undefine small_routine end end -- NEW_DEPENDENT私たちは、私たちのコンパイラによって私たちの歩みを止められたことでしょう。明らかな事実は、small_routine に正しく実行することを要求している big_routine が small_routine の定義を取り消すことから私たちを予防するのに十分である、ということです。
undefinition 句に関して置かれた二つ以上の制約があります。一つ目は、身近なものであり、あなたは、属性である特徴の定義を取り消してはいけません。つまり、ルーチンと関数だけが、定義を取り消すことができます。
次の制約は、ルーチンまたは関数であることだけが許されている、凍結<frozen>された特徴に関係します。継承者のどんな凍結された特徴も、子孫において、定義を取り消したり、再定義したり、名前を変更したりできません。
class THERMOMETER feature frozen decrease_temperature is do ... end; -- decrease_temperature end -- THERMOMETER class THERMISTOR inherit THERMOMETER undefine decrease_temperature end; -- 違法! end -- THERMISTORdecrease_temperature の定義を取り消すという THERMISTOR の企ては、許されません。このことは、凍結されていると宣言されたどんな特徴も、定義を取り消したり、再定義したり、名前を変更したりできるべきではないから、意味論的に意味を持ちます。
再定義の恩恵は、曖昧な多重継承のシナリオにおいて、より明らかになります。
class A feature f : INTEGER; end -- A class B feature f( arg1, arg2 : REAL ) is do ... end; -- f end -- B class C inherit A; B end; -- 違法 !! end -- C私たちが C において継承したいと願っている A または B のいずれかから、 f のバージョンを選択することを可能にした選択を、この章の 4.1 の内容から思い出してください。もし私たちが、次のように、C を修正するならば、
class C inherit A; B undefine f end; end -- Cいまや、f を A から継承することは可能になりますが、B からは不可能になります。Select 句で f の B のバージョンを選択することとは、どのように異なるのでしょう?
class C inherit A select f end; B; end -- C
[ 目次 ] [ イントロ | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Copyright ] [ html (full) | ps | pdf ]
[ Eiffel Liberty | GUERL | sOOap ]