(HASHIMOTO SOFTWARE CONSULTING)トップ
技術コラム
第4回:『科学的モデリング』継承④〜クラスとタイプ(型)
今回のコラムの話題は、
- クラスとタイプ(型)
- 「タイプ(型)継承(type inheritance)」と「実装継承(implementation inheritance)」
の解説です。
第1回から設計モデルに焦点をあてた継承の解説をしています。今回も継承の解説です。
継承の深い理解には、欠かすことのできないクラスの性質である「タイプ(型)」の解説からはじめ、
「タイプ(型)継承」と「実装継承」の説明に進んでいきます。
それからオブジェクト指向の書籍を読んでいると「クラス」という用語を用いずに、「タイプ(型)」という用語
を使うケースを良く目にします(洋書に多いです)。
例えば、「スーパータイプ」「サブタイプ」という様に書かれています。書籍の筆者が「クラス」と言う用語を
使わずに「タイプ(型)」を使用するのには明確な理由があります。「タイプ(型)」と「クラス」を使い分けること
に重要な意味があるからです。この点を理解するとオブジェクト指向の理解が一段深まります。
クラスの「タイプ(型)」を理解する
Java、C++、C#、Eiffel、Ada95などのオブジェクト指向言語は「強くタイプ(型)づけ」された言語と言われま
す。また、これらの言語はオブジェクト指向言語ですから「クラス」という概念をサポートしています(ActorやSelf
のように、「クラス」という概念をサポートしないオブジェクト指向言語もあります)。
この様な言語では「クラス=タイプ(型)」が成立します。
つまり、Java、C++、C#、Eiffel、Ada95などの言語でクラスを定義すれば、それが1つのタイプ(型)となりま
す。普通にこれらの言語でプログラムを作成しているときは、特別に「タイプ(型)」と「クラス」に違いを意識する
ことはないかもしれません。
「タイプ(型)」と「クラス」の違いを明確に理解するには、少し視点を変える必要があります。
そして、「タイプ(型)」と「クラス」の違いを理解すると、「タイプ(型)継承」と「実装継承」の違いが理解できます。
「タイプ(型)」と「クラス」の違いと、「タイプ(型)継承」と「実装継承」の違いを理解するには、継承関係にあ
るクラス間のタイプ(型)関係が大切になってきます。
具体的に説明していきましょう。
継承関係にあるクラスのタイプ(型)の関係に着目し、どのような関係にあるかを判定するには、クラスの定義に着目します。
コラムの第2回で「継承される特性(プロパティ)」を解説しました。スーパークラスとサブクラスの特性(プロパテ
ィ)に着目して判定します。今回は最も単純なところから解説をすすめますので、「継承される特性(プロパティ)」と
して属性と操作(メソッド/関数)のみを考えてみます。
タイプ(型)の同型
【図4-1】を見るとクラス「subclass」は、クラス「superclass」から特性(プロパティ)
である属性と操作(メソッド/関数)を継承しています。そして、クラス「subclass」は、新しい特性(プロパティ)を何も定義していませ
ん。【図4-1】の右の図は、クラス「subclass」が継承する特性(プロパティ)を明示的に表示しています。
UMLではクラス「superclass」から特性(プロパティ)を再定義(オーバーライド:override/redefine)せずに継
承するときは、明示的に表示しない規則がありますが、ここでは明確に示しています。
グレー色で表示している特性(プロパティ)が、クラス「superclass」から継承していることを表しています。
ここで、クラス「superclass」とクラス「subclass」の特性(プロパティ)を比較すると、当然ですが全く同じで
す。これをタイプ(型)から見て「同型」と判断します。
JavaやC++をはじめとするオブジェクト指向言語では、【図4-1】の場合でも、それぞれクラス「superclass」
のタイプ(型)とクラス「subclass」のタイプ(型)と別々の型が生成されますが、タイプ(型)の内容から見れば同じです[注4-1]。
ただし、【図4-1】のような継承関係は通常はメリットがありませんから、このような継承はあまり定義されませ
ん。継承によって、サブクラスのタイプ(型)がどのように変化するかを【表4-1】にまとめました。以降はこの表に
ついて説明していきます。
[注4-1]:
操作(メソッド/関数)の引数にはオブジェクトを指し示すポインタ変数を暗黙的に持ちます。これを考慮すると厳
密には全く同じタイプ(型)ではありませんが、ここでの議論にはポインタ変数のタイプ(型)は直接関係しませんので
無視しています。今後コラムで「共変化(co-variance)」について解説する時にポインタ変数を含めて議論します。
タイプ(型)の拡張(extension)
【図4-2】のケースを考えます。左の継承関係ではクラス「subclass」に新しい特性(プロパティ)である属性
「int a1」、操作「getA3():int」「setA3(int a3):void」が定義されています。
このとき、クラス「subclass」のタイプ(型)は、クラス「superclass」のタイプ(型)を「拡張した」と表現します。
新しい特性(プロパティ)を追加した分だけ、タイプ(型)が拡張されているからです
タイプ(型)の特殊化(specialization)
真ん中の継承関係ではクラス「subclass」が、クラス「superclass」から継承した特性(プロパティ)である操作「getA2():int」「setA2(int a2):void」を再定義(オーバーライド:override/redefine)しています。
ブルー色で表示されているのは、2つの操作が、再定義(オーバーライド:override/redefine)されていることを示しています(HSCI製のツールで自動に判定しブルー色で表示します。)
このとき、クラス「subclass」のタイプ(型)は、クラス「superclass」のタイプ(型)を「特殊化した(specialization)」と表現
します。継承した「getA2():int」「setA2(int a2):void」の処理をクラス「subclass」に最適化したからです。つまり、スーパークラスよりもサブクラスでタイプ(型)の特性(プロパティ)が、「具体的」
かつ「詳細化」されています。
注意点は、【図4-2】の真ん中の継承関係ではクラス「subclass」が、自分の新しい特性(プロパティ)を追加して
いませんのでタイプ(型)の構造は同型です。しかし、継承している2つの操作を再定義(オーバーライド:override
/redefine)しているのでタイプ(型)は「特殊化(specialization)」されています。
タイプ(型)の制限(restriction)
右の継承関係ではクラス「subclass」が、クラス「superclass」から、特性(プロパティ)を
部分的に継承しています。操作「setA2(int a2):void」が継承されていません。
このとき、クラス「subclass」のタイプ(型)は、クラス「superclass」のタイプ(型)を「制限した(restriction)」と表現
します。大部分のオブジェクト指向言語では「制限した(restriction)」の継承はできませんが、言語の中には可能なものもあります。
さて、ここまで継承関係にあるクラスのタイプ(型)について着目してきました。クラスとタイプ(型)を比較した表を【表4-2】に掲載します。
【表4-2】では「「クラス」から見た関係」では、常に「スーパークラス-サブクラス関係」が成立しますが、
「「タイプ(型)」から見た関係」では、「スーパークラス-サブクラス関係」が成立していても「スーパータイプ
(super-type)-サブタイプ(subtype)」になるとは限らないことが分かります。
このように「タイプ(型)」としてみた時は、「スーパークラス-サブクラス」関係との違いが明確になります。
包摂(subsumption)
さて、ここからが重要な話題になります。
オブジェクト指向設計や実装には「多態(ポリモフィズム/多相)」という技法があります。この「多態(ポリモフィ
ズム/多相)」の実現は、通常「継承」のメカニズムを用います。
この「多態(ポリモフィズム/多相)」させるためには、スーパークラスのタイプ(型)とサブクラスのタイプ(型)の間に
包摂(subsumption)が成立していないといけません
「タイプ(型)継承」とは、スーパークラスとサブクラスのタイプ(型)間に、「スーパータイプ(supertype)-サブタイ
プ(subtype)」の関係が存在する継承です。これまで見てきたように、クラスとタイプ(型)の間には対応関係がありますが、
常に「包摂(subsumption)」が成立する訳ではありません。「実装継承」の場合は、「包摂関係(subsumption)」が成立しません。
ここで少し継承の分類を整理します。色々な研究者が継承について研究し分類しています。
タイプ(型)継承」であれば、「多態(ポリモフィズム/多相)」を成立する可能性があります。
「可能性がある」と書いたのは、「タイプ(型)継承」であっても必ずしも正しい多態(ポリモフィズム/多相)が成立
する訳では無いからです。
「振る舞いの継承(behavioral subtype)」と「タイプ(型)置換原理」
「多態(ポリモフィズム/多相)」を成立させるのは、「タイプ(型)継承」の中でも、「振る舞いの継承(behavioral inheritance)」
と呼ばれるものが重要になります。「振る舞いの継承(behavioral subtype)」は、コンパイラーが判断できる文法レベルの整合性を満たす継承では
なく、「スーパークラス-サブクラス関係」の「意味(振る舞い)」の整合性も満たす継承です。このときはサブクラスは、スーパークラスのサブクラスであると同時に、「振る舞いサブタイプ(型)」となります。
それでは、「振る舞いの継承(behavioral subtype)」であることを判定するにはどうしたら良いでしょうか?
それには「タイプ(型)置換原理」と呼ばれる原理を利用することにします。
「タイプ(型)置換原理」は、「スーパークラス-サブクラス関係」にあるクラスが、「多態(ポリモフィズム/多相)」
を実現可能であるかどうかについて条件を述べている原則です。
「タイプ(型)置換原理」を理解するためには、もう少し継承に関連する他の知識が必要になります。
例えば、今回のコラムでは、特性(プロパティ)として属性と操作だけを比較し、「スーパークラス-サブクラス関
係」のタイプ(型)関係を判定しました。実はこれはクラスとタイプ(型)の考えを分かり易く説明するために、
単純化しています。実際にはもっと多くの、特性(プロパティ)について比較しなければなりません。
そこで、「タイプ(型)置換原理」については、機会を改めて別のコラムで詳しく解説します。
「is-a」関係/「a-kind-of」関係による判定は役に立たない
今回のコラムの最後に、継承関係や継承階層の作成のガイドとして利用されている「is-a」関係/「a-kind-of」関係について言及します。
書籍やセミナーやトレーニングでは、継承を考える時に、スーパークラスとサブクラス間に「is-a」関係/「a-
kind-of」関係が成立することをチェックように進めているものが多くあります。「is-a」関係/「a-kind-of」関係が
成立すれば、正しい継承関係にあると説明しています。この「正しい継承関係にある」とはどのような意味でしょう
か?漠然としたガイドラインであり科学的な視点からの判定は困難です。
実際のところ「is-a」関係/「a-kind-of」関係による「スーパークラス-サブクラス関係」の判定はあまり役に立ちません。
過去の継承の解説で「実装継承」についての有効性も紹介しましたが、「実装継承」ではスーパークラスとサブク
ラス間に「is-a」関係/「a-kind-of」関係が成立しません。
よって、「is-a」関係/「a-kind-of」関係は、【表4-4】の中の「意味的継承(semantic inheritance)」に対して
利用されているガイドラインとなっています。
しかし、設計や実装のモデルにおいて、正確に「意味的継承(semantic inheritance)」の継承関係や、「意味的継
承(semantic inheritance)」の継承階層が構築されているかどうかを検討するなら「タイプ(型)置換原理」を利用し
かありません。「タイプ(型)置換原理」を理解するまでには少しおぼえることがありますが、継承関係の正当性を判断するのは
この原理しかありません。
この理由は、是非、引き続き本コラムをご覧いただきください。きっと納得されると思います。そして継承につい
て理解を深めてください。