14

これは非常に物議を醸すトピックです。「いいえ」と言う前に、それは本当に、本当に必要なのでしょうか?

私は約 10 年間プログラミングをしていますが、他の方法では解決できなかった問題を継承によって解決したときのことを思い出すことができるとは正直言えません。一方で、継承を使用したときのことを何度も思い出すことができます。それは、そうしなければならないと感じたから、または自分が賢いのにその代償を払ってしまったからです。

実装の観点から、継承の代わりに集約または別の手法を使用できない状況は実際には見当たりません。

これに対する私の唯一の注意点は、インターフェイスの継承を引き続き許可することです。

(アップデート)

「必要な場合もある」と言うのではなく、なぜそれが必要なのか、例を挙げてみましょう。それは本当にまったく役に立ちません。あなたの証拠はどこにありますか?

(更新 2 コード例)

これは、継承のない、より強力でより明示的な IMO である従来の shape の例です。現実の世界では、何かが実際に他の何かの「である」ということはほとんどありません。ほとんどの場合、「Is Implemented in Terms」の方が正確です。

public interface IShape
{
    void Draw();
}

public class BasicShape : IShape
{
    public void Draw()
    {
        // All shapes in this system have a dot in the middle except squares.
        DrawDotInMiddle();
    }
}

public class Circle : IShape
{
    private BasicShape _basicShape;

    public void Draw()
    {
        // Draw the circle part
        DrawCircle();
        _basicShape.Draw();
    }
}

public class Square : IShape
{
    private BasicShape _basicShape;

    public void Draw()
    {
        // Draw the circle part
        DrawSquare();
    }
}
4

20 に答える 20

20

これについては、ちょっと前に奇抜なアイデアとしてブログに書きました。

削除する必要はないと思いますが、適切でない場合に継承を思いとどまらせるために、デフォルトでクラスを封印する必要があると思います。あると強力なツールですが、チェーンソーのようなものです。仕事に最適なツールでない限り、本当に使いたくないものです。そうしないと、手足を失い始める可能性があります。

ミックスインなどの潜在的な言語機能があり、IMO なしで生活しやすくなります。

于 2008-12-11T23:18:31.907 に答える
10

継承は、基本クラスに派生クラスごとに同じ実装を持つ多数のメソッドがある場合に、派生クラスごとにボイラープレート コードを実装する必要がないようにするのに役立ちます。Streamたとえば、次のメソッドを定義する.NETクラスを取り上げます。

public virtual int Read(byte[] buffer, int index, int count)
{
}

public int ReadByte()
{
    // note: this is only an approximation to the real implementation
    var buffer = new byte[1];
    if (this.Read(buffer, 0, 1) == 1)
    {
        return buffer[0];
    }

    return -1;
}

継承が利用可能であるため、基本クラスはReadByteすべての実装に対してメソッドを実装でき、それらを気にする必要はありません。クラスには、デフォルトまたは固定の実装を持つ、このような他のメソッドが多数あります。したがって、この種の状況では、すべての人にすべてを再実装させるか、StreamUtil呼び出すことができる型クラスを作成する (yuk!) というオプションがあるインターフェイスと比較して、持つことは非常に価値があります。

明確にするために、継承を使用して、DerivedStreamクラスを作成するために書く必要があるのは次のようなものです。

public class DerivedStream : Stream
{
    public override int Read(byte[] buffer, int index, int count)
    {
        // my read implementation
    }
}

一方、インターフェイスとメソッドのデフォルト実装を使用している場合は、StreamUtilさらに多くのコードを記述する必要があります。

public class DerivedStream : IStream
{
    public int Read(byte[] buffer, int index, int count)
    {
        // my read implementation
    }

    public int ReadByte()
    {
        return StreamUtil.ReadByte(this);
    }
}

}

したがって、コードが大幅に増えるわけではありませんが、これにクラスのいくつかのメソッドを掛けると、代わりにコンパイラが処理できる不必要な定型的なものになります。必要以上に物事を実装するのが面倒なのはなぜですか? 継承がすべてではないと思いますが、正しく使用すると非常に便利です。

于 2008-12-11T23:23:24.083 に答える
6

メンバー変数への委任を容易にするメカニズムを言語が提供してくれることを願っています。たとえば、インターフェイス I に 10 個のメソッドがあり、クラス C1 がこのインターフェイスを実装しているとします。C1 に似ているがメソッド m1() がオーバーライドされたクラス C2 を実装したいとします。継承を使用しない場合、次のようにします (Java で)。

public class C2 implements I {
    private I c1;

    public C2() {
       c1 = new C1();
    }

    public void m1() {
        // This is the method C2 is overriding.
    }

    public void m2() {
        c1.m2();
    }

    public void m3() {
        c1.m3();
    }

    ...

    public void m10() {
        c1.m10();
    }
}

つまり、メソッド m2..m10 の動作をメンバー変数 m1 に委譲するコードを明示的に記述する必要があります。それは少し痛いです。また、コードが乱雑になるため、クラス C2 の実際のロジックがわかりにくくなります。また、インターフェイス I に新しいメソッドが追加されるたびに、これらの新しいメソッドを C1 に委譲するためだけに、明示的にコードを C1 に追加する必要があることも意味します。

C1 は I を実装していますが、C1 に I からのメソッドが欠落している場合は、メンバー変数 c1 に自動的に委譲します。それはC1のサイズをちょうど

public class C2 implements I(delegate to c1) {
    private I c1;

    public C2() {
       c1 = new C1();
    }

    public void m1() {
        // This is the method C2 is overriding.
    }
}

言語でこれができるようになれば、継承の使用を避けるのがはるかに簡単になります。

これは、自動委任について書いたブログ記事です。

于 2008-12-12T12:53:26.497 に答える
6

もちろん、オブジェクトや継承がなくても、すばらしいプログラムを楽しく書くことができます。関数型プログラマーは常にそれを行います。しかし、性急にならないようにしましょう。このトピックに興味がある人は、Objective Caml のクラスとモジュールに関する Xavier Leroy の招待講演のスライドをチェックしてください。Xavier は、さまざまな種類のソフトウェアの進化のコンテキストにおいて、継承がうまくいくこととうまくいかないことをうまく説明しています。

すべての言語はチューリング完全なので、もちろん継承は必要ありません。しかし、継承の価値についての議論として、私はSmalltalk ブルーブック、特にCollection 階層Number 階層を紹介します。熟練したスペシャリストが、既存のシステムを混乱させることなく、まったく新しい種類の番号 (またはコレクション) を追加できることに、私は非常に感銘を受けました。

また、継承の「キラー アプリ」である GUI ツールキットについて質問者に思い出させます。適切に設計されたツールキット (見つけた場合) を使用すると、新しい種類のグラフィカル インタラクション ウィジェットを非常に簡単に追加できます。

そうは言っても、継承には生来の弱点があり (プログラム ロジックがクラスの大規模なセットに塗りつぶされている)、めったに使用されず、熟練した専門家のみが使用する必要があると思います。コンピュータ サイエンスの学士号を取得して卒業した人は、継承についてほとんど何も知りません。そのような人は、必要に応じて他のクラスから継承することを許可されるべきですが、他のプログラマーが継承するコードを決して書くべきではありません。その仕事は、自分が何をしているかを本当に知っているマスタープログラマーのために予約されるべきです. そして、彼らはしぶしぶやるべきです!

まったく異なるメカニズムを使用して同様の問題を解決する興味深い方法については、Haskell 型クラスを調べてみてください。

于 2008-12-12T06:31:19.537 に答える
5

ToString() と GetHashcode() にアクセスできるように、すべてのクラスに ISystemObject を実装して楽しんでください。

さらに、ISystemWebUIPage インターフェースで頑張ってください。

継承が気に入らない場合は、.NET を一緒に使用するのをやめることをお勧めします。時間を節約できるシナリオが多すぎます (「DRY: 繰り返さない」を参照してください)。

継承を使用するとコードが壊れる場合は、一歩下がって設計を再考する必要があります。

私はインターフェイスの方が好きですが、特効薬ではありません。

于 2009-01-08T05:57:40.687 に答える
5

継承は使用できるツールの 1 つであり、もちろん悪用される可能性もありますが、クラスベースの継承を削除するには、言語にさらに変更を加える必要があると思います。

主に C# 開発である現時点での私の世界を考えてみましょう。

マイクロソフトがクラスベースの継承を廃止するには、インターフェイスを処理するためのより強力なサポートを組み込む必要があります。内部オブジェクトへのインターフェイスを接続するためだけに、大量の定型コードを追加する必要があるアグリゲーションのようなもの。とにかくこれを行う必要がありますが、そのような場合は必須になります。

つまり、次のコードです。

public interface IPerson { ... }
public interface IEmployee : IPerson { ... }
public class Employee : IEmployee
{
    private Person _Person;
    ...

    public String FirstName
    {
        get { return _Person.FirstName; }
        set { _Person.FirstName = value; }
    }
}

これは基本的にもっと短くする必要があります。そうしないと、クラスが人を十分に模倣できるようにするためだけに、次のような多くのプロパティが必要になります。

public class Employee : IEmployee
{
    private Person _Person implements IPerson;
    ...
}

これにより、必要なコードを作成する代わりに、必要なコードを自動作成できます。オブジェクトを IPerson にキャストした場合、内部参照を返すだけではうまくいきません。

したがって、クラスベースの継承が取り除かれる前に、物事をより適切にサポートする必要があります。

また、可視性などを削除します。インターフェイスには、実際には 2 つの可視性設定があります。存在するものと存在しないものです。場合によっては、他の誰かがあなたのクラスをより簡単に使用できるようにするためだけに、より多くの内部データを公開することを余儀なくされるかもしれません。

クラスベースの継承では、通常、子孫が使用できるいくつかのアクセス ポイントを公開できますが、外部のコードは公開できません。通常、これらのアクセス ポイントを削除するか、すべての人に公開する必要があります。どちらの選択肢も好きかどうかはわかりません。

私の最大の疑問は、例として、C# のような新しい言語であるがクラスベースの継承を持たない D# を構築することが計画されていたとしても、そのような機能を削除する具体的なポイントは何であるかということです。つまり、まったく新しい言語を構築する計画を立てたとしても、最終的な目標が何であるかはまだ完全にはわかりません。

適切な手に渡っていない場合に悪用される可能性のあるものを削除することが目標ですか? もしそうなら、最初にアドレスを見たいと思っているさまざまなプログラミング言語の長いリストがあります。

そのリストの一番上: Delphiのwithキーワード。そのキーワードは、自分の足を撃つようなものではなく、編集者が散弾銃を購入し、家に来て狙いを定めるようなものです。

個人的にはクラスベースの継承が好きです。確かに、あなたは隅に自分自身を書くことができます. しかし、私たちは皆それを行うことができます。クラスベースの継承を削除します。私は自分自身を撃つ新しい方法を見つけるだけです。

さて、そのショットガンはどこに置いた...

于 2008-12-11T23:53:18.437 に答える
4

プロダクション コードでは、継承をほとんど使用しません。私はすべてにインターフェイスを使用しています (これはテストに役立ち、可読性を向上させます。つまり、インターフェイスを見てパブリック メソッドを読み取り、適切な名前のメソッドとクラス名のために何が起こっているかを確認できます)。私が継承を使用するのは、ほとんどの場合、サード パーティのライブラリがそれを要求するときだけです。インターフェイスを使用しても同じ効果が得られますが、「委任」を使用して継承を模倣します。

私にとって、これは読みやすいだけでなく、はるかにテストしやすく、リファクタリングも非常に簡単になります.

テストで継承を使用する唯一の方法は、システム内のテストの種類を区別するために使用する独自の TestCases を作成することです。

だから私はおそらくそれを取り除くことはありませんが、上記の理由からできるだけ使用しないことにしました.

于 2008-12-11T23:33:20.613 に答える
3

いいえ。継承が必要な場合もあります。そして、そうでない場合は、使用しないでください。いつでもインターフェイスを (インターフェイスを持つ言語で) 「ただ」使用することができ、データのない ADP は、インターフェイスを持たない言語のインターフェイスのように機能します。しかし、必ずしも必要ではないと感じたからといって、時には必要な機能を削除する理由はないと思います。

于 2008-12-11T23:16:30.600 に答える
3

私は悪魔の代弁者を演じなければならないと思います。継承がなければ、テンプレート メソッド パターンを使用する抽象クラスを継承することはできません。これが .NET や Java などのフレームワークで使用されている例はたくさんあります。Java のスレッドはそのような例です。

// Alternative 1:
public class MyThread extends Thread {

    // Abstract method to implement from Thread
    // aka. "template method" (GoF design pattern)
    public void run() {
        // ...
    }
}

// Usage:
MyThread t = new MyThread();
t.start();

別の方法は、私の意味では、使用する必要がある場合は冗長です。視覚的なクラッタの複雑さが増します。これは、実際に使用する前にスレッドを作成する必要があるためです。

// Alternative 2:
public class MyThread implements Runnable {
    // Method to implement from Runnable:
    public void run() {
        // ...
    }
}

// Usage:
MyThread m = new MyThread();
Thread t = new Thread(m);
t.start();
// …or if you have a curious perversion towards one-liners
Thread t = new Thread(new MyThread());
t.start();

私の悪魔の擁護者に脱帽することで、2 番目の実装のメリットは、テスト可能なクラスの設計に役立つ依存関係の注入または懸念の分離であると主張できると思います。インターフェイスが何であるかの定義 (少なくとも 3 つ聞いたことがあります) によっては、抽象クラスをインターフェイスと見なすことができます。

于 2008-12-12T06:57:02.623 に答える
2

継承は本当に必要ですか?「本当に」が何を意味するかによります。理論的には、パンチ カードやトグル スイッチのフリックに戻ることもできますが、ソフトウェアを開発するにはひどい方法です。

手続き型言語では、そうです、クラスの継承は確かな恩恵です。特定の状況でコードをエレガントに整理する方法を提供します。他の機能を過度に使用しないように、過度に使用しないでください。

たとえば、このスレッドの digiarnie の場合を考えてみましょう。彼/彼女はほとんどすべてにインターフェースを使用しますが、これは多くの継承を使用するのと同じくらい悪いです (おそらくそれよりも悪いです)。

彼のポイントのいくつか:

これはテストに役立ち、読みやすさを向上させます

どちらにもなりません。実際にインターフェイスをテストすることはなく、常にオブジェクト、つまりクラスのインスタンス化をテストします。そして、まったく別のコードを見る必要があると、クラスの構造を理解するのに役立ちますか? 私はそうは思わない。

ただし、深い継承階層についても同様です。理想的には、1 か所だけを見たいと考えています。

インターフェイスを使用しても同じ効果が得られますが、「委任」を使用して継承を模倣します。

委任は非常に良いアイデアであり、多くの場合、継承の代わりに使用する必要があります (たとえば、戦略パターンはまさにこれを行うことです)。ただし、インターフェイスでは動作をまったく指定できないため、インターフェイスは委譲とはまったく関係ありません。

また、リファクタリングが非常に簡単になります。

インターフェースへの初期のコミットメントは、通常、変更する場所が増えるため、リファクタリングを容易にするのではなく、困難にします。変更されるクラスがインターフェイスを実装していない場合、デリゲート クラスを引き出すのは簡単であるため、継承を早期に過剰に使用することは、インターフェイスを過剰に使用するよりも優れています (まあ、悪いことではありません)。そして、有用なインターフェイスを取得するよりも、これらのデリゲートからのものであることがよくあります。

したがって、継承の使いすぎは悪いことです。インターフェイスの使いすぎは良くありません。そして理想的には、クラスは何からも継承せず (「オブジェクト」または同等の言語を除く)、インターフェイスを実装しません。しかし、それはどちらの機能も言語から削除する必要があるという意味ではありません。

于 2008-12-12T12:38:42.733 に答える
2

いいえ、必要ありませんが、全体的なメリットが得られないわけではありません。これは、絶対に必要かどうかを心配するよりも重要だと思います.

結局のところ、ほとんどすべての最新のソフトウェア言語構造はシンタックス シュガーに相当します。本当に必要な場合は、アセンブリ コードを作成する (またはパンチ カードを使用する、または真空管を使用する) ことができます。

「is-a」関係を本当に表現したいとき、継承は非常に便利だと思います。継承は、その意図を表現する最も明確な手段のようです。すべての実装の再利用に委任を使用すると、その表現力が失われます。

これは虐待を許しますか?もちろんそうです。開発者がクラスから継承する方法を尋ねる質問をよく見かけますが、そのメソッドはサブクラスに存在してはならないため、メソッドを非表示にします。その人は明らかに継承のポイントを見逃しており、代わりに委任に向けられるべきです。

必要だから継承を使用するのではなく、仕事に最適なツールである場合があるため使用します。

于 2008-12-12T00:06:06.913 に答える
2

いいえ。頻繁に必要とされないからといって、まったく必要ないわけではありません。ツールキットの他のツールと同様に、誤用される可能性があります (そして、これまでも、今後も悪用される可能性があります)。ただし、決して使用してはいけないというわけではありません。実際、一部の言語 (C++) では、言語レベルで「インターフェース」のようなものがないため、大幅な変更がなければ禁止できませんでした。

于 2008-12-11T23:17:43.377 に答える
2

多くの場合、標準機能があるという理由だけで、インターフェイスよりも基本クラスを選択していることに気づきます。C# では、拡張メソッドを使用してそれを実現できるようになりましたが、それでもいくつかの状況で同じことを実現できません。

于 2008-12-11T23:18:31.113 に答える
1

継承によって時々達成されるコードの再利用のためのより良いメカニズムは特性であると私は信じています。トレイトとミックスインの違いや、トレイトが好まれる理由など、これに関するすばらしい議論については、このリンク(pdf)を確認してください。

C#(pdf)に特性を導入するいくつかの研究があります。

Perlには、Moose::Rolesを介した特性があります。Scalaの特性は、Rubyのようにミックスインようなものです。

于 2010-02-02T21:41:05.680 に答える
1

これがトピックの素晴らしい見方です:

IS-STRICTLY-EQUIVALENT-TO-A by Reg Braithwaite

于 2008-12-12T12:11:33.163 に答える
1

私の最初の考えは、あなたはクレイジーだということでした。でも、ちょっと考えてみたら、あなたの意見に賛成です。クラス継承を完全に削除すると言っているわけではありません (たとえば、部分的な実装を伴う抽象クラスが役立つ場合があります)。コード。

于 2008-12-11T23:26:31.923 に答える
1

継承とは、親クラスを分離して派生クラスを単体テストするために、依存性注入によって基本クラスの機能を提供することができなくなったことを意味することに注意してください。

したがって、依存性注入について非常に真剣に考えている場合 (私はそうではありませんが、そうすべきかどうか疑問に思っています) は、いずれにせよ、継承をあまり活用することはできません。

于 2008-12-11T23:43:29.737 に答える
0

問題は、「(非インターフェース型の) 継承をプログラミング言語から削除する必要があるか?」ということです。

私は「いいえ」と言います。既存のコードの多くが壊れてしまうからです。

それはさておき、インターフェイスの継承以外に継承を使用する必要がありますか? 私は主に C++ プログラマーであり、インターフェイスの多重継承とそれに続くクラスの単一継承のチェーンという厳密なオブジェクト モデルに従います。具象クラスはコンポーネントの「秘密」であり、それは友人であるため、何が起こっているのかは誰もビジネスではありません。

インターフェイスの実装を支援するために、テンプレート ミックスインを使用します。これにより、インターフェイスの設計者は、一般的なシナリオのインターフェイスを実装するのに役立つコードのスニペットを提供できます。コンポーネント開発者として、インターフェイス デザイナーが自分のクラスをどのように構築するべきか考えたことに邪魔されることなく、再利用可能なビットを入手するためにミックスイン ショッピングに行くことができると感じています。

そうは言っても、ミックスイン パラダイムは C++ にかなり固有のものです。これがなければ、実用的なプログラマーにとって継承は非常に魅力的であると思います。

于 2009-01-08T07:12:18.927 に答える