トピック

汎化 ページの先頭へ

現実の世界で発生する多くの事柄は、共通の性質を持っています。たとえば、犬と猫は共に動物です。同様に、オブジェクトも共通のプロパティを持つことがあります。オブジェクトの共通のプロパティを明らかにするためには、オブジェクトクラス間の汎化を使用します。共通のプロパティを独自のクラスへと抽出することによって、将来のシステム変更、保守を容易にできます。

汎化は、あるクラスがほかのクラスを継承することを示します。継承するクラスを、子孫と呼びます。継承されるクラスを祖先と呼びます。継承は、属性、関係、そのオブジェクトの操作などの全プロパティを含む祖先の定義が、子孫のオブジェクトにも有効であることを意味します。汎化は、子孫クラスから、その祖先クラスへと引き出されます。

汎化は、いくつかの段階において起こる場合があり、複雑な、複数レベルの継承階層をモデル化することを可能にします。一般のプロパティは、継承階層の上部に配置し、特殊なプロパティは下部に配置されます。つまり、汎化を使用することにより、一般的な概念の特化をモデル化することができます。

リサイクル マシン システムにおいて、全クラス (Can、Bottle、Crate) で、異なるタイプの廃棄物を記述します。タイプが同じであるほかに、これらには 2 つの共通プロパティがあります。高さと重さです。独立した Deposit Item というクラスで、属性と操作を通じて、これらのプロパティをモデル化できます。Can、Bottle、Crate がこのクラスのプロパティを継承します。

Can、Bottle、Crate のクラスが、Height と Weight という共通のプロパティを持つ。これらの各クラスが、一般概念である Deposit Item の特化。

多重継承 ページの先頭へ

クラスは、多重継承を通じて、ほかの複数のクラスから継承できます。ただし、通常は 1 つだけのクラスから継承します。

多重継承を使用する場合、問題となり得る点を次に示します。

  • クラスが、複数のクラスから継承する場合、祖先にある関係、操作、属性の名前を調べます。いくつかの祖先において同じ名前が使用されている場合は、これが特定の継承クラスにとって持つ意味を記述する必要があります。たとえば、その名前を限定することによって、それを宣言したソースを示します。
  • 継承の繰り返しを使用する場合は、同じ祖先が 1 回以上、子孫によって継承されます。これが発生したときは、継承階層が、次に示す「ダイヤモンド型」になります。

多重継承および繰り返し継承。Scrolling Window With Dialog Box クラスが、Window クラスを 1 回以上継承している。

この例で、「Scrolling Window With Dialog Box のインスタンスに、Window 属性のコピーが、いくつあるのか」という疑問を持つかもしれません。したがって、繰り返し継承を使用した場合は、その意味を明確にした定義が必要です。ほとんどの場合は、多重継承をサポートするプログラミング言語によって定義します。

一般的に、多重継承を制御するプログラミング言語のルールは複雑で、多くの場合は正確な使用が困難です。したがって、多重継承は、必要な場合のみに使用し、常に慎重に行うことを推奨します。

抽象クラスと具象クラス ページの先頭へ

インスタンス化されておらず、ほかのクラスによって継承されるためだけに存在するクラスが、抽象クラスです。実際にインスタンス化されているクラスが、具象クラスです。抽象クラスが役立つためには、少なくとも 1 つの子孫を持つ必要があることに注意します。

倉庫処理システムにある Pallet Place が、パレットの位置の異なるタイプに共通するプロパティを表現する抽象エンティティ クラスです。このクラスは、具象クラスの Station、Transporter、Storage Unit によって継承され、これらすべてが倉庫内でパレットの位置として行動できます。これらの全オブジェクトには共通する 1 つのプロパティがあります。1 つ以上の Pallet を保持できることです。

継承されたクラス。ここでは Pallet Place は抽象クラスで、それ自身ではインスタンス化されません。

使用 ページの先頭へ

クラスのステレオタイプは異なる目的を持つため、任意のクラスのステレオタイプからほかへ継承することは無意味です。たとえば、バウンダリ クラスにエンティティ クラスを継承させると、バウンダリ クラスがある種の混合物になってしまいます。したがって、汎化は、同じステレオタイプのクラス間のみに使用します。

クラス間の 2 つの関係を表現するために、汎化を使用できます。

  • サブタイプ化。子孫が、祖先のサブタイプであることを指定します。サブタイプ化とは、子孫が祖先の構造と振る舞いを継承していること、および子孫が祖先のタイプであることを意味します (つまり、子孫とは、いかなる状況でもその祖先の代わりになれるサブタイプ)。
  • サブクラス化。子孫が祖先のサブクラスであること (サブタイプではない) を指定します。サブクラス化とは、子孫が祖先の構造と振る舞いを継承していること、および子孫が祖先のタイプでないことを意味します。

上記のような関係を作成するには、いくつかのクラスに共通するプロパティを発生させ、ほかのクラスによって継承される独立したクラスにそのプロパティを配置する方法があります。または、汎化されたクラスを特化する新しいクラスを作成して、その汎化クラスから継承させる方法もあります。

2 つの変形が同時に存在する場合、クラス間に正しい継承を簡単に設定できます。ただし、いくつかの場合においては、これらは一致せず、継承の使用方法を理解可能に保つための努力が必要です。少なくとも、モデル内にある各継承関係の目的を知っておく必要があります。

多形性をサポートする継承 ページの先頭へ

サブタイプ化とは、いかなる状況でも子孫がその全祖先の代わりとなるサブタイプであることを意味します。サブタイプ化とは多形性の特殊なケースであり、重要なプロパティです。祖先の子孫となる可能性のあるものを考慮せずに、全クライアント (祖先を使用するオブジェクト) の設計を可能にするからです。これによって、クライアント オブジェクトをより一般的で再利用可能にします。クライアントが実際のオブジェクトを使用するとき、クライアントは特定の方法で働き、オブジェクトは常にタスクを行います。サブタイプ化は、システムがサブタイプのセットへの変更に耐えることを確実にします。

倉庫処理システムにおいて、Transporter Interface クラスは、クレーンやトラックなど、輸送設備の全タイプとコミュニケーションするための基本機能を定義します。クラスは、中でも、excuteTransport 操作を定義します。

Truck Interface クラスと Crane Interface クラスは、Transporter Interface を継承します。つまり、両方のクラスのオブジェクトが、executeTransport メッセージに反応します。オブジェクトはいつでも Transporter Interface の代役となることができ、そのすべての振る舞いを提供します。したがって、ほかのオブジェクト (クライアント オブジェクト) は、Truck Interface または Crane Interface オブジェクトが反応するかを知らずに、Transporter Interface オブジェクトにメッセージを送ることができます。

Transporter Interface クラスは、それだけでインスタント化することのない抽象になることもできます。この場合、Transporter Interface は、excuteTransport 操作のシグネチャだけを定義することがあり、子孫クラスがこれを実装します。

C++ など、オブジェクト指向言語のいくつかは、クラス階層をタイプ階層として使用します。このとき、設計者は、設計モデルでサブタイプ化するために継承を使用する必要があります。Smalltalk-80 などのその他の言語は、コンパイル時にタイプのチェックがありません。オブジェクトが、受理したメッセージに応答できない場合は、エラー メッセージを生成します。

タイプのチェックがない言語でも、サブタイプの関係を示すために汎化を使用するのはよい考えです。オブジェクト モデルとソース コードの理解や保守を容易にするために、その言語で可能かどうかにかかわりなく、汎化を使用する必要がある場合もあります。継承の使用方法が適切なスタイルかどうかは、プログラミング言語の規則に大きく依存します。

実装の再利用をサポートする継承 ページの先頭へ

サブクラス化は、汎化の再利用の局面を構成します。サブクラス化するときは、ほかのクラスに定義されたプロパティを継承することによって再利用できる実装の部分を考慮します。サブクラス化によって作業を減らし、特定のクラスを実装するときはコードの再利用が可能になります。

Smalltalk-80 のクラス ライブラリでは、Dictionary クラスは Set からプロパティを継承します。

この汎化の理由は、Dictionary が一般のメソッドおよび格納戦略のいくつかを、Set の実装から再利用できるためです。Dictionary を Set (1 組の主要な値を含む) と見なすことができるとしても、Dictionary は Set のサブタイプではありません。なぜなら、Dictionary にいかなるオブジェクトでも追加できるわけではないからです (1 組の主要な値のみ)。Dictionary を使用するオブジェクトは、これが実際は Set であることを知りません。

サブクラス化を行うと、理解や保守が困難な非論理的な継承階層になってしまうことがしばしばあります。したがって、再利用のみを目的とした継承の使用はお勧めしません。プログラミング言語の使用において別に推奨がある場合を除きます。こういった再利用の保守は、通常かなり扱いにくいものです。クラス Set への変更は、クラス Set を継承する全クラスへの大きな変更を意味します。このことに注意して、安定したクラスだけを継承します。継承によって、クラス Set の実装は事実上凍結されます。変更作業は非常にコストがかかるからです。

プログラミング言語における継承 ページの先頭へ

設計における汎化関係の使用は、プログラミング言語における継承の意味と提案される使用方法に大きく依存します。オブジェクト指向言語は、クラス間の継承をサポートしますが、非オブジェクト指向言語はこれをサポートしません。言語の特性は、設計モデルで処理します。継承または多重継承をサポートしない言語を使用する場合は、実装において継承のシミュレートを行う必要があります。このとき、設計モデルでこのシミュレーションをモデル化し、継承の構造を記述するためには汎化を使用しない方が賢明です。汎化によって継承構造をモデル化し、その後実装で継承のシミュレートを行うと、設計を破壊することがあります。

継承または多重継承をサポートしない言語を使用する場合は、実装において継承のシミュレートを行う必要があります。このとき、設計モデルでこのシミュレーションをモデル化し、継承構造を記述するためには汎化を使用しない方が賢明です。汎化によって継承構造をモデル化し、その後実装で継承をシミュレートだけすると、設計を破壊することがあります。

おそらく、シミュレーション中にインターフェイスおよびその他のオブジェクト プロパティを変更する必要が生じます。継承を次の方法のいずれかで実行することをお勧めします。

  1. 子孫から祖先へメッセージを転送して実行します。
  2. 各子孫にある祖先のコードを複写して実行します。この場合、祖先クラスは作成されません。

この例では、関連のインスタンスであるリンクを通して、子孫が祖先にメッセージを転送します。

Can オブジェクト、Bottle オブジェクト、Crate オブジェクトに共通する振る舞いを、特殊クラスに割り当てました。この振る舞いが共通するオブジェクトが、必要に応じて振る舞いを実行するために、オブジェクト Deposit Item にメッセージを送ります。



Rational Unified Process   2003.06.15