web-dev-qa-db-ja.com

なぜ継承を使用するのですか?

質問が 前に説明した であったことは知っていますが、継承は少なくとも時には作曲よりも望ましいという仮定の下に常にあるようです。理解を深めることを期待して、この仮定に挑戦したいと思います。

私の質問はこれです:Sinceあなたは古典的な継承でできるオブジェクト合成で何でも達成できますand since継承は非常に頻繁に乱用されます[1]and sinceオブジェクトの構成により、デリゲートオブジェクトのランタイムを柔軟に変更できますever従来の継承を使用しますか?

委任に便利な構文を提供しないJavaやC++などのいくつかの言語で継承を推奨する理由をある程度理解できます。これらの言語では、いつでも継承を使用することで多くの入力を節約できますしかし、Objective CやRubyのような他の言語は、委任のための非常に便利な構文の両方を古典的な継承andを提供します。 Goプログラミング言語は、私の知る限り、古典的な継承は価値があるよりも厄介であり、コードの再利用のための委任のみをサポートしていると判断した唯一の言語です。

私の質問を述べる別の方法はこれです:古典的な継承が特定のモデルを実装するのに間違っていないことを知っていても、その理由は構成の代わりにそれを使用するのに十分ですか?

[1]多くの人々は、クラスにインターフェースを実装させる代わりに、古典的な継承を使用してポリモーフィズムを実現しています。継承の目的はコードの再利用であり、ポリモーフィズムではありません。さらに、一部の人々は「is-a」関係の直感的な理解をモデル化するために継承を使用します これはしばしば問題になる可能性があります

更新

継承について話すときの意味を明確にしたいだけです。

クラスが部分的にまたは完全に実装された基本クラスから継承する継承の種類 について話しています。私はnotインターフェースを実装するのと同じことを意味する純粋に抽象的な基本クラスから継承することについて話しているが、記録に関しては議論していない。

更新2

私は、継承がC++でポリモーフィズムを達成する唯一の方法であることを理解しています。その場合、それを使用しなければならない理由は明らかです。したがって、私の質問は、ポリモーフィズムを実現するための明確な方法を提供するJavaまたはRuby)のような言語に限定されます(それぞれインターフェイスとアヒルのタイピング)。

60
KaptajnKold

明示的にオーバーライドしていないすべてを同じインターフェイスを実装する他のオブジェクト(「ベース」オブジェクト)に委任すると、基本的に構成に加えてGreenspunned継承になりますが、(ほとんどの言語で)より多くの冗長性がありますそして定型句。継承の代わりに構成を使用する目的は、委任したい動作のみを委任できるようにすることです。

明示的にオーバーライドされない限り、オブジェクトが基本クラスのすべての動作を使用するようにしたい場合、継承は最も単純で、最も冗長で、最も単純な表現方法です。

11
dsimcha

継承の目的はコードの再利用であり、ポリモーフィズムではありません。

これはあなたの根本的な間違いです。ほぼ正反対が当てはまります。 primary(public)継承の目的は、問題のクラス間の関係をモデル化することです。多態性はその大部分です。

正しく使用された場合、継承とは既存のコードを再利用することではありません。むしろ、使用されていることですby既存のコード。つまり、既存の基本クラスと連携できる既存のコードがある場合、その既存の基本クラスから新しいクラスを派生させると、他のコードも新しい派生クラスと自動的に連携できるようになります。

コードの再利用に継承を使用することは可能ですが、そうする場合/使用する場合は、通常、パブリック継承ではなくプライベート継承にする必要があります。使用している言語が委任を十分にサポートしている場合、プライベート継承を使用する理由がほとんどないという可能性はかなり高いです。 OTOH、プライベート継承は、委任(通常)がサポートしないいくつかのことをサポートします。特に、この場合、ポリモーフィズムは明らかに二次的な懸念事項ですが、canは依然として懸念事項です-つまり、プライベート継承ではalmostである基本クラスから開始できます=必要なもの、および(許可されていると仮定して)正しくない部分をオーバーライドします。

委任では、既存のクラスをそのまま使用することが唯一の本当の選択です。望みどおりの結果が得られない場合、唯一の本当の選択は、その機能を完全に無視し、ゼロから再実装することです。場合によっては損失はありませんが、他の場合はかなり重要です。基本クラスの他の部分がポリモーフィック関数を使用する場合、プライベート継承によりonlyポリモーフィック関数をオーバーライドでき、他の部分はオーバーライドされた関数を使用します。委任では、新しい機能を簡単にプラグインできないため、既存の基本クラスの他の部分はオーバーライドしたものを使用します。

48
Jerry Coffin

継承を使用する主な理由は、構成の形式としてnotです。これは、多態的な動作を取得できるようにするためです。ポリモーフィズムが必要ない場合は、少なくともC++では継承を使用しないでください。

16
anon

ポリモーフィズムが継承の大きな利点であることは誰もが知っています。継承にあるもう1つの利点は、実世界のレプリカを作成できることです。たとえば、給与管理システムでは、スーパークラスEmployeeを使用してこれらすべてのクラスを継承する場合、マネージャー開発者、オフィスボーイなどに対処します。これらのクラスはすべて基本的に従業員であることが、現実世界の文脈でプログラムをより理解しやすくします。そしてもう1つ、クラスにはメソッドが含まれているだけでなく、属性も含まれています。そのため、社会保障番号の年齢など、従業員クラスの従業員に一般的な属性を含めると、コードの再利用と概念の明確性、そしてもちろんポリモーフィズムが向上します。ただし、継承を使用する場合は、基本的な設計原則「変化するアプリケーションの側面を識別し、変化する側面から分離する」ことに留意する必要があります。継承によって変化するアプリケーションの側面を、代わりに構成を使用して実装しないでください。また、変更できない側面については、明らかな「is a」関係が存在する場合、継承継承を使用する必要があります。

7
faisalbhagat

次の場合、継承が優先されます。

  1. 拡張するクラスのAPI全体を公開する必要があります(委任では、多くの委任メソッドを記述する必要があります)andあなたの言語は、「すべての未知のメソッドを委任する」という簡単な方法を提供していませんに"。
  2. 「友人」という概念のない言語の保護フィールド/メソッドにアクセスする必要があります
  3. 言語で多重継承が許可されている場合、委任の利点は多少低下します
  4. 言語が実行時にクラスまたはインスタンスからも動的に継承できる場合、通常、委任はまったく必要ありません。どのメソッドを公開するか(およびどのように公開するか)を同時に制御できる場合は、まったく必要ありません。

私の結論:委任は、プログラミング言語のバグの回避策です。

6
Aaron Digulla

継承を使用する前に、慎重を期す必要があるため、常に考え直します。とはいえ、最も洗練されたコードを生成するだけの場合も多くあります。

4
ChaosPandion

インターフェイスは、オブジェクトができることを定義するだけで、方法は定義しません。簡単に言えば、インターフェースは単なるコントラクトです。インターフェイスを実装するすべてのオブジェクトは、独自のコントラクトの実装を定義する必要があります。実際には、これによりseparation of concern。事前にそれらを知らないさまざまなオブジェクトを処理する必要があるアプリケーションを作成することを想像してください。まだそれらを処理する必要があります。知っているのは、それらのオブジェクトが行うべきすべての異なることです。したがって、インターフェイスを定義し、コントラクトのすべての操作に言及します。次に、そのインターフェースに対してアプリケーションを作成します。後であなたのコードやアプリケーションを活用したい人は誰でも、システムで動作するようにオブジェクトにインターフェースを実装する必要があります。インターフェースは、コントラクトで定義された各操作の実行方法をオブジェクトに強制的に定義させます。このように、誰でもインターフェースを実装するオブジェクトを作成して、それらをシステムに完璧に適応させることができます。知っているのは実行する必要があることと、実行方法を定義する必要があるオブジェクトだけです。

実際の開発では、このプラクティスは一般的にProgramming to Interface and not to Implementation

インターフェースは単なる契約または署名であり、実装については何も知りません。

インターフェースに対するコーディングとは、クライアントコードは常にファクトリーによって提供されるインターフェースオブジェクトを保持することです。ファクトリーによって返されるインスタンスは、任意のファクトリー候補クラスが実装しなければならないインターフェース型になります。このように、クライアントプログラムは実装を心配せず、インターフェイスシグネチャがすべての操作を実行できるかどうかを決定します。これは、実行時にプログラムの動作を変更するために使用できます。また、メンテナンスの観点からはるかに優れたプログラムを作成するのに役立ちます。

基本的な例を次に示します。

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

[STAThread]
static void Main()
{
    //This is your client code.
    ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
    speaker.Speak();
    Console.ReadLine();
}

public interface ISpeaker
{
    void Speak();
}

public class EnglishSpeaker : ISpeaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : ISpeaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak German.");
    }

    #endregion
}

public class SpanishSpeaker : ISpeaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    #endregion
}

代替テキストhttp://ruchitsurati.net/myfiles/interface.png

テンプレートメソッドパターンはどうですか?カスタマイズ可能なポリシーのポイントが大量にある基本クラスがあるとします。but戦略パターンは、少なくとも次のいずれかの理由で意味がありません。

  1. カスタマイズ可能なポリシーは、基本クラスについて知る必要があり、基本クラスでのみ使用でき、他のコンテキストでは意味がありません。代わりに戦略を使用することはできますが、基本クラスとポリシークラスの両方が相互に参照を持つ必要があるため、PITAです。

  2. ポリシーを自由に組み合わせて一致させるのは理にかなわないという点で、ポリシーは互いに結合されています。それらはすべての可能な組み合わせの非常に限られたサブセットでのみ意味をなします。

2
dsimcha

古典的な継承の主な有用性は、インスタンス変数/プロパティを操作するメソッドに対して同一のロジックを持つ多数の関連クラスがある場合です。

それを処理するには、本当に3つの方法があります。

  1. 継承。
  2. コードを複製します( code smell "Duplicated code")。
  3. ロジックをさらに別のクラスに移動します(コードは「Lazy Class」、「Middle Man」、「Message Chains」、および/または「Inappropriate Intimacy」の匂いがします)。

現在、継承の誤用が発生する可能性があります。たとえば、Javaにはクラス InputStream および OutputStream があります。これらのサブクラスは、読み取り/書き込みファイル、ソケット、配列、文​​字列、およびいくつかは、他の入力/出力ストリームをラップするために使用されます。

0
Powerlord

あなたが書いた:

[1]多くの人々は、クラスにインターフェースを実装させる代わりに、古典的な継承を使用してポリモーフィズムを実現しています。継承の目的はコードの再利用であり、ポリモーフィズムではありません。さらに、一部の人々は、継承を使用して、しばしば問題となる可能性のある「is-a」関係の直感的な理解をモデル化します。

ほとんどの言語では、「インターフェースの実装」と「別のクラスからのクラスの派生」の間の境界線は非常に細いです。実際、C++などの言語では、クラスAからクラスBを派生させ、Aが純粋な仮想メソッドのみで構成されるクラスである場合、areインターフェースを実装します。

継承はインターフェイスの再利用であり、実装の再利用ではありません。上で書いたように、コードの再利用についてはnotです。

正しく指摘しているように、継承はIS-A関係をモデル化することを目的としています(多くの人がこれを間違っているという事実は、継承自体とは関係ありません)。 「BEHAVES-LIKE-A」と言うこともできます。ただし、何かが他の何かとIS-A関係を持っているからといって、同じ(または類似の)コードを使用してこの関係を実現することを意味するわけではありません。

データを出力するさまざまな方法を実装するこのC++の例を比較してください。 2つのクラスは(パブリック)継承を使用して、多相的にアクセスできるようにします。

struct Output {
  virtual bool readyToWrite() const = 0;
  virtual void write(const char *data, size_t len) = 0;
};

struct NetworkOutput : public Output {
  NetworkOutput(const char *Host, unsigned short port);

  bool readyToWrite();
  void write(const char *data, size_t len);
};

struct FileOutput : public Output {
  FileOutput(const char *fileName);

  bool readyToWrite();
  void write(const char *data, size_t len);
};

次に、これがJavaであるかどうかを想像してください。 「出力」は構造体ではなく、「インターフェース」でした。 「書き込み可能」と呼ばれる場合があります。 「public output」の代わりに、「implements Writable」と言います。設計に関する限り、違いは何ですか?

なし。

0
Frerich Raabe

完全にOOPではありませんが、コンポジションは通常、余分なキャッシュミスを意味します。依存しますが、データを近づけることはプラスです。

一般的に、私はあなたが得ることができる最高のあなた自身の判断とスタイルを使用して、いくつかの宗教的な戦いに行くことを拒否します。

0
bestsss

あなたが尋ねたとき:

特定のモデルを実装するために古典的な継承が間違っていないことを知っていても、その理由は構成の代わりにそれを使用するのに十分ですか?

答えはいいえだ。モデルが(継承を使用して)正しくない場合、何を使用しても間違っています。

私が見た継承に関するいくつかの問題を以下に示します。

  1. 派生クラスポインターのランタイムタイプを常にテストして、キャストできる(またはダウンできる)かどうかを確認する必要があります。
  2. この「テスト」はさまざまな方法で実現できます。クラス識別子を返す何らかの仮想メソッドがあるかもしれません。または、RTTI(Run time type identification)(少なくともc/c ++では)を実装する必要があるかもしれませんが、これはパフォーマンスに影響を与える可能性があります。
  3. 「キャスト」を取得できないクラスタイプは、潜在的に問題がある可能性があります。
  4. クラス型を継承ツリーで上下にキャストするには多くの方法があります。
0
C Johnson

継承を使用する最も便利な方法の1つは、GUIオブジェクトです。

0
Brad Barker