継承を使用する状況

更新 : 2007 年 11 月

継承というプログラミング概念は、便利である反面、使い方を間違いやすくもあります。継承よりインターフェイスを使用した方がよい場合もよくあります。このトピックと「インターフェイスを使用する状況」では、継承とインターフェイスを場合によってどのように使い分けるかを説明します。

次の場合には、継承の方が適しています。

  • 継承の階層が has-a 関係ではなく is-a 関係を表している場合。

  • 基本クラスのコードを再利用できる場合。

  • 同じクラスやメソッドを別のデータ型に適用する必要がある場合。

  • クラスの階層構造が浅く、他の開発者によって多くのレベルが追加される可能性も低い場合。

  • 基本クラスの変更をすべての派生クラスに反映させる場合。

次に、上の各項目について順番に詳しく説明します。

継承と is-a 関係

オブジェクト指向プログラミングにおけるクラスの関係は、is-a と has-a の 2 つの関係で表されます。is-a 関係では、派生クラスは確実に基本クラスの一種です。たとえば、PremierCustomer というクラスは Customer という基本クラスの関係は is-a 関係です。これは、重要顧客 (premier customer) は顧客 (customer) の一種であるためです。一方、CustomerReferral というクラスと Customer クラスの関係は has-a 関係です。これは、顧客紹介 (customer referral) には顧客 (customer) という語が含まれてはいますが、顧客の一種ではないためです。

継承階層のオブジェクトは、基本クラスで定義されているフィールド、プロパティ、メソッド、およびイベントを継承するため、基本クラスとの間に必ず is-a 関係を持ちます。他のクラスとの間に has-a 関係を持つクラスは、不適切なプロパティやメソッドを継承してしまう可能性があるため、継承階層には向きません。たとえば、CustomerReferral クラスを上述の Customer クラスから派生させたとすると、ShippingPrefs や LastOrderPlaced などの無用なプロパティも継承されます。このような has-a 関係を表すには、関係のないクラスやインターフェイスを使用します。次の図は、is-a 関係と has-a 関係の例です。

is-a 関係と has-a 関係

基本クラスとコードの再利用

継承には、コードを再利用できるという利点もあります。適切にデザインされたクラスは、一度デバッグするだけで、新しいクラスの基本クラスとして何度でも再利用できます。

コードの効果的な再利用の例は、データ構造体を管理するライブラリによく見られます。たとえば、数種類のインメモリ リストを管理する大規模なビジネス アプリケーションがあったとします。リストの 1 つは顧客データベースのインメモリ コピーであり、処理を高速化するためにセッションの開始時にデータベースから読み込まれます。このデータ構造体は、たとえば次のようになります。

Class CustomerInfo
    Protected PreviousCustomer As CustomerInfo
    Protected NextCustomer As CustomerInfo
    Public ID As Integer
    Public FullName As String

    Public Sub InsertCustomer(ByVal FullName As String)
        ' Insert code to add a CustomerInfo item to the list.
    End Sub

    Public Sub DeleteCustomer()
        ' Insert code to remove a CustomerInfo item from the list.
    End Sub

    Public Function GetNextCustomer() As CustomerInfo
        ' Insert code to get the next CustomerInfo item from the list.
        Return NextCustomer
    End Function

    Public Function GetPrevCustomer() As CustomerInfo
        'Insert code to get the previous CustomerInfo item from the list.
        Return PreviousCustomer
    End Function
End Class

このアプリケーションには、次の例に示すように、ユーザーが買い物カゴに追加した製品のリストもあります。

Class ShoppingCartItem
    Protected PreviousItem As ShoppingCartItem
    Protected NextItem As ShoppingCartItem
    Public ProductCode As Integer
    Public Function GetNextItem() As ShoppingCartItem
        ' Insert code to get the next ShoppingCartItem from the list.
        Return NextItem
    End Function
End Class

上の 2 つのリストには同じパターンがあります。この 2 つのリストは、対象となるデータ型が違うだけで、動作は同じ (挿入、削除、および取得) です。本質的に同じ機能を持つ 2 つのコード ベースを管理することは、非効率です。この場合は、次に示すように、リストの管理専用のクラスを作成し、データ型ごとにそのクラスを継承するのが最も効率的なソリューションです。

Class ListItem
    Protected PreviousItem As ListItem
    Protected NextItem As ListItem
    Public Function GetNextItem() As ListItem
        ' Insert code to get the next item in the list.
        Return NextItem
    End Function
    Public Sub InsertNextItem()
        ' Insert code to add a item to the list.
    End Sub

    Public Sub DeleteNextItem()
        ' Insert code to remove a item from the list.
    End Sub

    Public Function GetPrevItem() As ListItem
        'Insert code to get the previous item from the list.
        Return PreviousItem
    End Function
End Class

ListItem クラスのデバッグは一度で済みます。ListItem クラスを使用して他のクラスを作成するときには、リストの管理について考える必要はありません。たとえば、次のようになります。

Class CustomerInfo
    Inherits ListItem
    Public ID As Integer
    Public FullName As String
End Class
Class ShoppingCartItem
    Inherits ListItem
    Public ProductCode As Integer
End Class

継承ベースのコードの再利用は強力なツールとなる反面、リスクも付随します。どれほど優れたデザインに基づくシステムであっても、予想外の変更が起こることはあります。既存のクラスの階層構造を変更すると、予想外の結果が生じることがあります。このような例については、「配置後の基本クラス デザインの変更」の「壊れやすい基本クラスの問題」を参照してください。

交換できる派生クラス

クラスの階層構造内の派生クラスを基本クラスと交換して使用できる場合もあります。このプロセスは、継承ベースのポリモーフィズムと呼ばれます。継承ベースのポリモーフィズムでは、インターフェイス ベースのポリモーフィズムの利点に、基本クラスのコードの再利用やオーバーライドという継承の利点が組み合わされています。

継承ベースのポリモーフィズムは、たとえば、描画パッケージで有効に利用できます。次のコードでは、継承は使用されていません。

Sub Draw(ByVal Shape As DrawingShape, ByVal X As Integer, _
    ByVal Y As Integer, ByVal Size As Integer)

    Select Case Shape.type
        Case shpCircle
            ' Insert circle drawing code here.
        Case shpLine
            ' Insert line drawing code here.
    End Select
End Sub

この方法には、多少問題があります。たとえば、他のユーザーが後で楕円のオプションを追加するにはソース コードを変更する必要がありますが、そのユーザーがソース コードにアクセスすることさえできない場合も考えられます。また、これほどはっきりした問題ではありませんが、楕円を描画するには、(楕円には大小 2 つの直径があるため) 線のケースとは関係のない別のパラメータが必要になります。さらに折れ線 (接続された複数の線) を追加する場合は、その他のケースには関係のない別のパラメータがまた追加されます。

継承を使用すると、これらの問題をほとんど解決できます。適切にデザインされた基本クラスでは、特定のメソッドの実装は派生クラスに委ねられているため、どのような図形にも対応できます。他の開発者は、基本クラスのドキュメントを使用して、派生クラスでメソッドを実装できます。クラスのその他の項目 (x 座標や y 座標など) は、すべての派生クラスが使用するため、基本クラスに組み込むことができます。たとえば、次に示すように、Draw を MustOverride メソッドにします。

MustInherit Class Shape
    Public X As Integer
    Public Y As Integer
    MustOverride Sub Draw()
End Class

次に、それぞれの図形に応じた要素をクラスに追加します。たとえば、Line クラスに必要なのは Length フィールドだけです。

Class Line
    Inherits Shape
    Public Length As Integer
    Overrides Sub Draw()
        ' Insert code here to implement Draw for this shape.
    End Sub
End Class

この方法を使用すると、ソース コードにアクセスできない他の開発者も、必要に応じて、新しい派生クラスを使用して基本クラスを拡張できます。たとえば、Line クラスから Rectangle というクラスを派生させることができます。

Class Rectangle
    Inherits Line
    Public Width As Integer
    Overrides Sub Draw()
        ' Insert code here to implement Draw for the Rectangle shape.
    End Sub
End Class

この例は、レベルごとに実装の詳細を追加することによって、汎用のクラスから特殊なクラスを作成する方法を示しています。

この段階で、派生クラスが本当に is-a 関係を表しているか、誤って has-a 関係を使用していないかを再度確認することをお勧めします。たとえば、新しい四角形のクラスが線だけで構成されている場合は、継承が最適な方法とは言えません。しかし、新しい四角形が幅のプロパティを持つ線である場合は、is-a 関係が維持されます。

クラスの浅い階層構造

継承は、クラスの階層構造が比較的浅い場合に最も適しています。クラスの階層構造が極端に深く複雑になると、開発が難しくなります。クラスの階層構造を使用するかどうかを決定するときには、クラスの階層構造を使用するメリットとそれに伴う複雑さとを考慮する必要があります。階層数の上限は、6 レベルとするのが一般的です。ただし、クラスの階層数の上限は、各レベルの複雑さの程度など、多くの要因に左右されます。

基本クラスを通じたすべての派生クラスの変更

継承の最も強力な機能の 1 つは、基本クラスに対する変更を派生クラスに反映させることです。これをうまく利用すると、1 つのメソッドの実装を更新するだけで、数十、数百もの派生クラスで新しいコードを使用できます。ただし、他のユーザーがデザインした派生クラスで問題が発生する可能性があるため、この操作には注意が必要です。この操作を実行するときには、元の基本クラスを使用しているクラスと新しい基本クラスとの互換性を確認する必要があります。特に、基本クラスのメンバの名前や型を変更するのは避けてください。

たとえば、Integer 型のフィールドに郵便番号を格納する基本クラスをデザインしたとします。他の開発者は、この郵便番号フィールドを継承して派生クラスを作成しています。この郵便番号フィールドには 5 桁の数値を格納できますが、今度は郵便番号が拡張され、新たにハイフン (-) と 4 桁の数値を加えることになったとします。この場合、10 文字の文字列を格納できるように基本クラスのフィールドを変更してしまうと、他の開発者は、新しいサイズとデータ型を使用するために、派生クラスを変更および再コンパイルする必要があります。これは最悪のシナリオです。

基本クラスを変更するときには、新しいメンバを追加するのが最も安全です。たとえば、上の例に新しいフィールドを追加して、郵便番号の追加の 4 桁をそこに格納するようにします。これにより、新しいフィールドを使用するようにクライアント アプリケーションを更新できます。既存のアプリケーションが使用できなくなることはありません。基本クラスを拡張できる機能は、インターフェイスにはない継承階層の大きな利点です。

参照

概念

インターフェイスを使用する状況

配置後の基本クラス デザインの変更