HASHIMOTO SOFTWARE CONSULTING - 神奈川県 座間市
(HASHIMOTO SOFTWARE CONSULTING)トップ

技術コラム

第7回:『科学的モデリング』継承⑦〜「タイプ(型)指向モデリング」のススメ

  • 第7回の話題〜「オブジェクト指向」から「タイプ(型)指向モデリング」へ!

  • 第7回のコラムの話題は「タイプ」です。つまり「型」です。「型」と継承の関係について考えます。 企業の開発現場で利用されているJava、C++、C#などのオブジェクト指向のプログラミング言語の多くは、強く タイプ(型)付けされています。実開発に利用されているプログラミング言語の多くが強くタイプ(型)付けされ ているのは理由があります。
    それは設計や実装においてタイプ(型)を活用することで、設計が行いやすく品質を向上させることに極めて有効 だからです。

  • 強くタイプ(型)付けされたオブジェクト指向言語は、「タイプ(型)指向言語」と呼んでも良いくらいであり、もっ と言ってしまえば「タイプ(指向)プログラミング」をする言語といって良いでしょう。それだけに「タイプ(型)」の 理解が優れた設計と実装に大きく関わってきます【注7-1】。特に、継承関係の効果的な設計には「タイプ(型)」の理 解が大変重要となります。

  • そこで今回は、タイプ(型)を活用することでどのように品質を向上させることが可能なのかを探ります。

  • 今回のテーマは:

  • tc7_0
  • です。

  • 【注7-1】
    smalltalk,Rubyなどのような「動的タイプ(型)チェック」を行う言語でも、タイプ(型)の情報(の一部)をコンパイ ル時にチェックするか、実行時にチェックするかの違いはりますが、本コラムの解説内容は当てはまります。

  • 「クラス」と「タイプ(型)」の概念の区別

  • オブジェクト指向設計と実装では「クラス」と「タイプ(型)」の概念の区別が重要です。まずはここから解説をス タートしたいと思います。
  • オブジェクト指向言語の多くは、言語として「クラス」と「タイプ(型)」の概念を明確に区別する機能を直接サ ポートしていません。【図7-1】【図7-2】を見てください

  • tc7_1


  • 図7-2】を見るとクラス「人間」とクラス「男性」およびクラス「女性」が継承関係にあります。 Java、C++、C#などのオブジェクト指向言語では、サブクラスを定義すると、自動的に新しいタイプ(型)を同時 に定義することになります。

  • 例えば、【図7-2】のクラス図には、クラスが3つ定義されていますが、これは同時に 異なる3つのタイプ(型)を定義していることになります。

  • つまり、多くのオブジェクト指向言語では:

  • tc7_2a


  • ことになる訳です。
  • 【図7-2】のクラス図には、クラス「男性」とクラス「女性」は、スーパークラス「人間」の特性(プロパティ)を継 承するだけで新しい特性(プロパティ)が全く定義されていません。

  • このようなサブクラスで特性(プロパティ)を何も追加しない場合でも、このサブクラスのタイプ(型)をスーパーク ラスのタイプ(型)と別の型として定義します。クラスの定義によって暗黙的にタイプ(型)の定義をするために、タイ プ(型)定義をしているという意識が希薄になりやすいのです。

  • 「クラス定義 = タイプ(型)定義」ということは、Java、C++、C#などのオブジェクト指向言語を利用されている 方には極めて当たり前の話に感じるかも知れません。しかし、話は単純ではありません。第5回と第6回のコラムで は、クラスのタイプ(型)に意識を向けて設計や実装を行う重要性と共に、あるクラスから継承関係を用いてサブクラ スを定義したときに、サブクラスを定義しただけでは、そのタイプ(型)が「振る舞いサブタイプ(behaviorsubtype)」 には必ずしもならないことを解説しました。

  • 不変条件(制約)の継承と再定義(redefine)

  • クラスとタイプ(型)を意識的に区別して考える重要性を理解するために【図7-2】を少し詳しく見ていきます。

  • 【図7-3】はクラス「男性」とクラス「女性」がスーパークラス「人間」から継承している特性(プロパティ)を明示 的に表示しています。クラス「男性」とクラス「女性」の特性(プロパティ)は全て灰色で表示されているのでこの3 つのクラスの特性(プロパティ)は全く同じであることが分かります

  • つまり、3つのクラスは異なるタイプ(型)として解釈されますが、タイプ(型)のプロパティ(型)に注目すると実質的 には同じで違はありません。よって、タイプ(型)として『同型』と言えることになります【注7-2】。
    なお、タイプ(型)の『同型』『拡張』『特殊化』『制限』という区別については第4回のコラムを参照してください。

  • tc7_2


  • さて、前回の第6回のコラムで「タイプ(型)の整合性」を判定することで「多相(ポリモフィズム/多態)」が成立す る継承関係であるかどうかを判定できることを解説しました。
    「タイプ(型)の整合性」が成立するならば、スーパークラス「人間」のタイプ(型)の参照変数に、クラス「男性」 あるいはクラス「女性」のオブジェクト(インスタンス)の参照を問題なく代入できることになります。

  • つまりクラス「男性」あるいはクラス「女性」のタイプ(型)は、スーパークラス「人間」のタイプ(型)のサブタイ プ(部分型)になります【注7-3】。これを「振る舞いサブタイプ(behavior subtype」と呼びます。

  • 【注7-2】
    厳密にはタイプ名称や操作(メソッド/関数)の暗黙的な引数となるオブジェクトを指し示す参照変数のタイプ(型) も考慮する必要があります。その場合は厳密には全く同じタイプ(型)ではありませんが、ここでの議論に直接関係し ませんので無視しています

  • 【注7-3】
    OCaml言語のように「クラス=タイプ(型)」とは単純に考えない言語があります。2つのクラス間に継承関係が成 立しない場合でも、その2つのタイプ(型)間に「振る舞いサブタイプ」関係が成立させることが可能です。 ただし、今回はOCaml言語のような言語はとりえず意識せず、開発現場で広く使われているJava、C++、C#など の言語を前提して解説を行います。

  • タイプ(型)指向モデリング

  • 【図7-1】〜【図7-3】は「タイプ(型)の整合性」を検討するまでもない単純な例でした。
    そこでクラスとタイプ(型)を意識的に区別して考えることの重要性を理解するために別の例を使い解説します。
    【図7-4】に示す国会議員の種類とである衆議院議員と参議院議員について考えます。【図7-1】と【図7-4】を比 較すると非常に似た集合の図になっています。この事から【図7-2】と【図7-3】と似ているクラス図が作成されるこ とが推測できます。

  • そうであれば、スーパークラス「国会議員」のタイプ(型)の参照変数に、クラス「衆議院議員」あるいはクラス 「参議院議員」のオブジェクト(インスタンス)の参照を問題なく代入できることが推測できます。
    特に詳細に検討する必要がない印象を受けますが、念のため実際にクラス図を作成し確認してみましょう。

  • tc7_3


  • 【図7-5】はスーパークラス「国会議員」からクラス「衆議院議員」とクラス「参議院議員」が継承するクラス図で す。【図7-5】は【図7-2】のクラス図と非常に似ていますが、僅かに異なる点があります。
    スーパークラス「国会議員」の属性「年齢」に不変条件(制約)として{年齢>=25}が付与されています。 このことが「タイプ(型)の整合性」に影響を与えることを見ていきます。

  • 【図7-4】の集合の図を見れば分かる通り、日本の国会議員は衆議院議員と参議院議員から構成されます。衆議院議 員は25歳以上、参議院議員は30歳以上という法律ですので、国会議員全体の年齢の制約は25歳以上であることは妥 当です。クラス図は【図7-5】のようになります。

  • tc7_4


  • さて、今考えていることはスーパークラス「国会議員」のタイプ(型)の参照を持つ変数に、クラス「衆議院議員」 あるいはクラス「参議院議員」のオブジェクト(インスタンス)の参照を問題なく代入できるかどうかです。つまり、 「多相(ポリモフィズム/多態)」が成立するかどうかを判定する事でした。

  • 「多相(ポリモフィズム/多態)」が成立するかどうかの判定は、「タイプ(型)の整合性」を使用しなければなりま せんから、3つのクラスのタイプ(型)の整合性を確認します。そこでクラスのタイプ(型)の特性(プロパティ)をクラス 図に詳細に表示します。

  • tc7_5


  • 【図7-6】はクラス「衆議院議員」とクラス「参議院議員」がスーパークラス「国会議員」から継承している特性 (プロパティ)を明示的に表示しています。クラス図を見るとクラス「衆議院議員」のタイプ(型)は、スーパー クラス「国会議員」のタイプ(型)の特性(プロパティ)をそのまま継承しています。そのため、タイプ(型)として実 質『同型』であることが一目で分かります。

  • 一方、クラス「参議院議員」は、スーパークラス「国会議員」の年齢の不変条件(制約)を{>=30}で再定義してい ます。参議院議員は30歳以上だからです。スーパークラス「国会議員」の特性(プロパティ)をそのまま継承すると、 クラス「参議院議員」の「意味的な正しさ」は保証されません。そこで、年齢の不変条件(制約)を{>=30}で再定義 しています。これによりクラス「参議院議員」にとって適切な不変条件(制約)になります。

  • tc7_7


  • 不変条件(制約)を{>=30}で再定義したことにともない、操作「Set年齢(integer new年齢):void」の 事前条件(pre-condition)と事後条件(post-condition)も再定義されていることに気が付いたでしょうか。操作「Set年齢 (integer new年齢):void」の事前条件(pre-condition)は、この操作の「引数で渡される値が30歳以上でなければな らいことを課す制約」を意味します。
    操作「Set年齢(integer new年齢):void」の引数から参議院議員の年齢の不変条件(制約)を{>=30}を満たさない値が渡されることがありうるからです。

  • 事後条件(post-condition)は、この操作の処理に終了時に属性「年齢」の値が「この操作の引数で渡される値で更 新され、かつ、30歳以上であること」を保証することを示しています。

  • 結論としてクラス「参議院議員」自身は、クラスの「正当性」を保証するために、適切に特性(プロパティ)を再定義 したことが分かります。
    以上から、クラス「参議院議員」はタイプ(型)の『特殊化(specialization)』が行われています。
    不変条件や事前条件(pre-condition)と事後条件(post-condition)が再定義された場合もタイプ(型)の『特殊化 (specialization)』になるのです。

  • ここまでの検討で、クラス「国会議員」とクラス「参議院議員」の継承関係から分かったことは下記になります

  • tc7_8


  • 意味的に正しい継承関係を設計するには、まずクラスの正当性を保証する特性(プロパティ)を明確にすることが重要です。
    正当性を保証する特性(プロパティ)を明確にしないまま継承関係の「タイプ(型)の整合性」を判定することは無理だからです。

  • タイプ置換原理(type substitution principle)

  • さて、それでは「タイプ(型)の整合性」からスーパークラスとサブクラスの「多相(ポリモフィズム/多態)」が成立するかどうかの判定をします。
    ただし、その前に「多相(ポリモフィズム/多態)」が成立するかどうかの判定に関係する数学的方法を用いる原理:

  • 「タイプ(型)の意味的な整合性」を判定する「タイプ置換原理」
  • を紹介します。

  • 「タイプ置換原理」を用いると「タイプ(型)の整合性」からスーパークラスとサブクラスの「多相(ポリモフィズム/ 多態)」が成立するかどうかの判定が演繹的に可能になります。
  • 第5回と6回および今回のコラムのような簡単なクラス図では、「タイプ(型)の整合性」からスーパークラスとサブ クラスの「多相(ポリモフィズム/多態)」が成立するかについて判定するは簡単です。しかし、多くのソフトウエア 開発ではもっと専門的で複雑なクラスが多数定義され、かつ継承関係が利用されることになりますから、常に単純に 判定できるとは限りません。そこで、「タイプ置換原理」という数学的な方法が提案されています。

  • なお、「タイプ(型)置換原理」という名前は総称です。「タイプ(型)置換原理」は、多くの研究者から多数の方法が提案されています。
    今回はその中で「リスコフ置換原理(Liskov substitution principle)」を紹介します【注7-4】。「リスコフ置換原理」を最初に紹介する理由として下記が挙げられます

  • tc7_9

  • 最初に「リスコフ置換原理」がどのような原理なのかを把握しましょう。上記の①〜③の「リスコフ置換原理」を 含む「タイプ置換原理」の詳細については、今回から数回に渡って継承関係の解説の中で解説していきます。今回は 「タイプ置換原理」の1つである「リスコフ置換原理」を取り上げ、「タイプ置換原理」の特徴と価値の解説をします。

  • さて、「リスコフ置換原理」は色々な文献で紹介されています。本コラムでは分かり易さを重視して「リスコフ置 換原理」の紹介と解説をすることにします

  • tc7_10


  • 【表7-1】をもう少し具体的に表現すると【表7-2】のようになります。
  • tc7_11


  • 「リスコフ置換原理」について書いた【表7-1】と【表7-2】の両方が、クラスではなくタイプ(型)に焦点を置いて 述べている原理であることに注意してください。このことからも、クラスとタイプ(型)に概念を明確に区別してモデ ルを作成することが大切であることがわかると思います。

  • 「リスコフ置換原理」を含めたすべての「タイプ(型)置換原理」は、名称から分かる通りクラス間の継承関係の正 当性について、クラスのタイプ(型)を用いて判断します。

  • オブジェクト指向の研究者であり、Eiffelの言語設計者でもあるBertrand Meyerは、文献[7-3]の中で、クラスその ものよりもタイプ(型)に意識を向けて設計と実装を行うことで、ソフトウエア開発では大きな利益を得ることができると述べています。

  • 国会議員のクラス図に「リスコフ置換原理」適用してみましょう。

  • 属性の不変条件(invariant)&クラス不変条件(class-invariant)の比較
  • まず、クラス「衆議院議員」から考えます。【図7-8】を見てください。
    クラス「衆議院議員」の属性の不変条件(invariant)、クラス不変条件(class-invariant)および各操作の事前条件 (pre-condition)と事後条件(post-condition)は、スーパークラス「国会議員」と同じです。タイプ(型)が『同型』で あるからです。よって「リスコフ置換原理」を満たしています。これは簡単でした。

  • なお、操作「get氏名():String」と操作「get年齢():integer」の事前条件(pre-condition)と事後条件(post- condition)は設定されていません。これを明示的に表示する場合は{TRUE}と表示します。

  • tc7_12


  • 一方、クラス「参議院議員」の属性の不変条件(invariant)、クラス不変条件(class-invariant)は、 {年齢>=30歳}と条件が強く(厳しく)なっています。【表7-1】【表7-2】には明示的に掲載していませんが、サブクラスで属性 の不変条件(invariant)とクラス不変条件(class-invariant)の条件を強く(厳しく)すること自体は可能です(ただし、こ れが操作の各クラスの事前条件(pre-condition)と事後条件(post-condition)に影響を及ぼし問題になることがありま す。クラス「参議院議員」のケースがそれに該当します)。

  • tc7_13


  • 事前条件(pre-condition)&事後条件(post-condition)の比較

  • 3つの操作については、クラス「衆議院議員」は問題が無い事は一目瞭然ですので、クラス「参議院議員」に焦点をあてます【図7-9】。

  • tc7_14


  • クラス「参議院議員」が再定義している操作は、操作「set年齢(new年齢):void」だけなので、この操作について検討すれば十分です【図7-10】。

  • スーパークラス「国会議員」から継承している操作「set年齢(new年齢):void」の事前条件(pre-condition)が、{年齢>=30}と再定義されています。
    スーパークラス「国会議員」のこの操作の事前条件(pre-condition)は、{年齢>=25}ですから、サブクラスであるクラス「参議院議員」の方が、条件が強く(厳しく)なっています。これは「リスコフ置換原理」を満たしていません。

  • 以上から、「リスコフ置換原理」による国会議員の継承関係の判定結果は【表7-3】になります。

  • tc7_15


  • 【注7-4】
    「リスコフ置換原理(Liskov substitution principle)」は、頭文字をとって『LSP』と略されることがあります。

  • リスコフ置換原理の価値と注意点

  • 国会議員のクラス図から分かることは、「多相(ポリモフィズム/多態)」を前提とする継承関係では「リスコフ置換原理」を満たせない場合は継承関係に正当性がないことを意味します【注7-5】。
    クラス「国会議員」とクラス「参議院議員」間の継承関係は多相(ポリモフィズム/多態)が満たさないことが分かりましたが、これは多くの方が、最初の直観とは異なる結果だったのではないでしょうか。
    【図7-4】【図7-5】を見る限り問題無く多相(ポリモフィズム/多態)を実現する印象を受けるからです。

  • このようなごく簡単な継承関係でも正確に「振る舞いの整合性」を検討して、「リスコフ置換原理」のような「タイプ(型)置換原理」を適用してみないと継承関係の設計や実装の妥当性のある判定ができないことが分かります。

  • よって、クラス図を作成するときに、クラスのタイプ(型)に注意を払い、特性(プロパティ)を明確にすることが重要です。これをしないままモデリングしてもモデルの正当性を明確にせずに設計していることになってしまいます。 これでは良い品質のモデルや実装を開発することを放棄するのと同じです。

  • 【注7-5】
    「リスコフ置換原則」は最も厳しい規則の「タイプ(型)置換原理」です。そのため、「リスコフ置換原則」を満たせない継承関係も、他の専門家が提案している「タイプ(型)置換原理」で判定がOKになることもあります。

  • 考察

  • 原因
    クラス「国会議員」とクラス「参議院議員」間の継承関係において、タイプ置換が成立しない直接の原因は、スーパークラス「国会議員」から継承している操作「set年齢(new年齢):void」の事前条件(pre-condition)を{年齢 >=30}に再定義したことです。

  • しかし、原因の源流はクラス「参議院議員」の属性の不変条件(invariant) {年齢>=30歳}をスーパーよりも強く(厳しく)したことです。繰り返しますが、サブクラスで属性の不変条件(invariant)を強く(厳しく)すること自体は可能です。

  • ただし、強く(厳しく)再定義した不変条件(制約)を操作の各クラスの事前条件(pre-condition)と事後条件(post-condition)の中に用いると「リスコフ置換原理」を満たせなくなることが普通です。この点に注意してください。

  • スーパークラス「国会議員」の不変条件(invariant)は除外できない

  • クラス「国会議員」とクラス「参議院議員」間の継承関係において、タイプ置換が成立しない直接の原因は、スーパークラス「国会議員」から継承している操作「set年齢(new年齢):void」の事前条件(pre-condition)を{年齢 >=30}と再定義したことと、遠因がクラス「参議院議員」の属性の不変条件(invariant) {年齢>=30歳}をスーパーよりも強く(厳しく)したことにあるならば、いっそのことスーパークラスであるクラス「国会議員」の各条件を除いてしまうのはどうでしょうか?

  • 結論から言えば、これは絶対にできません。

  • 各クラスは必ずクラスの正当性を保証する特性(プロパティ)の条件を明確にしなければなりません。これはクラス「国会議員」のような抽象クラス(abstract class)にも当てはまります。

  • 抽象クラスはオブジェクト(インスタンス)を生成できませんが、外部クラスのインタフェースとなるので操作の呼び出し条件である「事前条件」と操作の処理結果を保証する「事後条件」を明確に明示する必要があります。

  • tc7_16


  • 2つのクライアントクラス(顧客クラス)

  • スーパークラスは「2つのクライアントクラス(顧客クラス)」を意識する必要があります【図7-12】。

  • 1つはクラスを利用する外部のクラスです。もう1つのクライアントは、継承関係によりサブクラスを定義するサブクラスです。

  • 継承関係は「振る舞い整合性」を満たさないといけないために、「リスコフ置換原理」のような「タイプ(型)置換 原理」を適用してスーパークラスの条件をサブクラス側でも満たすことを保証しなければなりません。そのために、 スーパークラスは必ず正当性を保証するタイプ(型)の特性(プロパティ)を明確に示さなければなりません。

  • クラスのタイプ(型)の特性(プロパティ)の中で、クラス不変条件(class-invariant)、事前条件(pre-condition)、 事後条件(post-condition)を、そのクラスの『表明(assertion)』と呼びます

  • スーパークラスに限らず全てのクラスは、この『表明(assertion)』を明確に定義して、自分のクラスの利用 者である外部のクラスに示す必要があります。特にスーパークラスでは、『表明(assertion)』が重要となりま す。スーパークラスでは、自分のクラスの利用者である外部のクラスに加えて、サブクラス(とその設計者)という もう1つのクライアントが存在するからです。

  • なお、クラスが正当性を保証するタイプ(型)の特性(プロパティ)である『表明(assertion)』は、設計や実装だ けでなく、クラスのインスペクションおよび試験にとっても絶対に必要になります。 そして、極めて効果的かつ妥当性があるインスペクションや試験が可能になります。インスペクションや試験 については別の機会にコラムで解説します。

  • tc7_17
    tc7_18


  • まとめ&次回

  • 今回の「タイプ(型)指向モデリング」はいかがだったでしょうか? 今回のコラムでご紹介した「タイプ(型)指向モデリング」は、クラスよりもタイプ(型)に意識を向けてモデリング や設計や実装を行います。そのようにすることで、今回紹介した「タイプ(型)置換原理」「リスコフ置換原則」など のような多くの原理や定理を用いることがきます。

  • 今回のコラムで1つ追記して置くことにします。

  • 多数提案されている「タイプ(型)置換原理」の中で、「リスコフ置換原則」は最も厳しい規則を持つ「タイプ(型) 置換原理」なっています。ここで、「最も厳しい規則」とは、置換原理が成立するための条件が多いと考えてくださ い。つまり多くの条件を見たさないとタイプ(型)同士が、置換可能と判定されないという事を意味します。

  • そのため、実際に設計や実装で継承関係を用いる時に、常に「リスコフ置換原則」を遵守することは困難なことが あります。また「リスコフ置換原則」遵守することが得策ではない時もあります。そして、「リスコフ置換原則」を 満たせない継承関係も、他の「タイプ(型)置換原理」を利用すると判定がタイプ(型)同士の置換が可能と判定される こともあります。

  • このような事を書くと、「タイプ(型)置換原理」が複雑で難しく思われるかもしれません。「タイプ(型)置換原 理」が難しいのではなく、継承関係が、奥が深く単純ではないのです。ですから、「タイプ(型)置換原理」のような 科学的で妥当性のある継承関係の判定方法を用いる必要があるのです。そして、「継承関係」はすばらしく大きな効 果を安全に利用する方法となるのです。

  • 継承階層の設計と維持は「タイプ(型)置換原理」

  • 「タイプ(型)置換原理」の中で「リスコフ置換原則」は最も厳しい規則と書きましたが、「リスコフのタイプ(型) 置換原則」遵守することが可能な継承階層を設計できるならば、それに越したことはありません。

  • 特に「リスコフのタイプ(型)置換原則」は継承階層の開発と保守に有効です。

  • 「リスコフのタイプ(型)置換原則」を満たす継承階層は,継承階層の中のクラスを修正・追加・削除した後であって も決して意味的な整合性が破壊されないことが分かっています。

  • よって「リスコフのタイプ(型)置換原則」を満たすように継承階層を設計できるならば、首尾一貫した科学的な原 理を用いて継承階層の正当性を確実に保証することができます【図7-11】。この点についても今後のコラムで 解説していきます。

  • tc7_19


  • 参考文献

  • 文献[7-1] [BARBARAH. LISKOV 1994] A Behavioral Notionof Subtyping
  • 文献[7-2] [Peter Wegner 1991] Concepts and Paradigmsof Object-Oriented Programming
  • 文献[7-3] [Bertrand Meyer 1997] Object-Oriented Software Construction(邦訳「オブジェクト指向入門 第2版-原則・コンセプト」「オブジェクト指向入門 第2版-方法論・実践」)
  • 文献[7-4] [Markku Sakkinen] Inheritance and Other Main Principles of C++ and Other Object-Oriented Languages
  • 文献[7-5] [Luca Cardelli , Peter Wegner1985] On Understanding Types,Data Abstraction, andPolymorphism
  • 文献[7-6] [Luca Cardelli] Typeful Programming
  • ページの先頭へ戻る
  • HASHIMOTO SOFTWARE CONSULTING INTERNATIONAL Inc./TEL 042-747-0766