63

質問は以前に議論されたことがあることを私は知っていますが、それは常に継承が少なくとも時々作曲よりも好ましいという仮定の下にあるようです。ある程度の理解を得るために、その仮定に挑戦したいと思います。

私の質問はこれです:古典的な継承でできるオブジェクトコンポジションで何でも達成でき、古典な継承は非常に頻繁に悪用されるため[1] オブジェクトコンポジションはデリゲートオブジェクトの実行時間を変更する柔軟性を提供するので、なぜこれを使用するのでしょうか古典的な継承?

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

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

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

アップデート

継承について話すとき、私が正確に何を意味するのかを明確にしたいだけです。

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

アップデート2

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

4

13 に答える 13

54

[注:この質問は元々、言語に依存しないものとしてタグ付けされていました。これに基づいて、この回答はかなり言語に依存しないように作成されているため、Smalltalk、C ++、ObjectPascalなどの幅広い言語で使用されている継承について説明します。それ以来、特にJavaに関するものとして再タグ付けされています。classJavaは、aとainterfaceを2つの完全に別個のものとして定義する点で異なります。継承の目的がコードの再利用であり、ポリモーフィズムではないという考えは、Java固有の観点からは合理的ですが、言語に依存しない観点からは明らかに間違っています。Javaだけを気にするなら、これはおそらく最良の答えではありません。]

継承の目的は、ポリモーフィズムではなく、コードの再利用です。

これがあなたの根本的な間違いです。ほぼ正反対です。(パブリック)継承の主な目的は、問題のクラス間の関係をモデル化することです。ポリモーフィズムはその大部分です。

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

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

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

于 2010-07-28T15:23:59.063 に答える
18

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

于 2010-07-28T09:52:32.470 に答える
16

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

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

于 2010-07-28T13:38:40.253 に答える
7

ポリモーフィズムが継承の大きな利点であることは誰もが知っています。継承で私が見つけたもう1つの利点は、実世界のレプリカを作成するのに役立つことです。たとえば、給与システムでは、これらすべてのクラスをスーパークラスの従業員と継承する場合、マネージャーの開発者、オフィスの男の子などを扱います。これらのクラスはすべて基本的に従業員であるということは、現実の世界で私たちのプログラムをより理解しやすくします。そしてもう1つ、クラスにはメソッドが含まれているだけでなく、属性も含まれています。したがって、社会保障番号の年齢など、従業員クラスの従業員に一般的な属性を含めると、コードの再利用と概念の明確さ、そしてもちろんポリモーフィズムが向上します。ただし、継承を使用する際に留意する必要があるのは、基本的な設計原則です。

于 2012-11-25T15:44:07.180 に答える
6

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

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

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

于 2010-07-28T09:56:16.233 に答える
4

継承はすぐにトリッキーになる可能性があるため、継承を使用する前に常によく考えます。そうは言っても、それが単に最もエレガントなコードを生成する場合が多くあります。

于 2010-07-28T13:41:31.857 に答える
3

インターフェイスは、オブジェクトが実行できることのみを定義し、方法は定義しません。つまり、簡単に言えば、インターフェースは単なる契約です。インターフェイスを実装するすべてのオブジェクトは、コントラクトの独自の実装を定義する必要があります。実際の世界では、これはあなたに与えます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

于 2010-07-28T10:22:22.227 に答える
2

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

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

  2. ポリシーは、それらを自由に組み合わせることが意味をなさないという点で互いに結合されています。それらは、考えられるすべての組み合わせの非常に限られたサブセットでのみ意味があります。

于 2010-07-28T13:33:55.137 に答える
0

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

これを処理するには、実際には3つの方法があります。

  1. 継承。
  2. コードを複製します(コードの臭い「重複したコード」)。
  3. ロジックをさらに別のクラスに移動します(コードは「レイジークラス」、「ミドルマン」、「メッセージチェーン」、および/または「不適切な親密さ」の臭いがします)。

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

于 2010-07-28T14:01:39.427 に答える
0

あなたが書いた:

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

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

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

正しく指摘しているように、継承は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だったと想像してみてください。「出力」は構造体ではなく、「インターフェース」でした。それは「書き込み可能」と呼ばれることがあります。「publicOutput」の代わりに、「implementsWritable」と言います。デザインに関してはどのような違いがありますか?

なし。

于 2010-07-28T10:14:22.040 に答える
0

あなたが尋ねたとき:

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

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

これが私が見た継承に関するいくつかの問題です:

  1. 派生クラスポインタの実行時型を常にテストして、キャストアップ(またはダウン)できるかどうかを確認する必要があります。
  2. この「テスト」は、さまざまな方法で実行できます。クラス識別子を返すある種の仮想メソッドがあるかもしれません。または、RTTI(実行時型識別)(少なくともc / c ++では)を実装する必要がある場合があります。これにより、パフォーマンスが低下する可能性があります。
  3. 'キャスト'を取得できないクラスタイプは、潜在的に問題になる可能性があります。
  4. クラスタイプを継承ツリーの上下にキャストする方法はたくさんあります。
于 2010-07-28T10:25:24.310 に答える
0

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

于 2010-07-28T14:18:46.163 に答える
0

完全にOOPではありませんが、それでも、構成は通常、余分なキャッシュミスを意味します。状況によって異なりますが、データを近づけることはプラスです。

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

于 2011-02-09T10:15:48.723 に答える