8

私は PHP の世界から来たのですが、なぜ開発者がコンストラクター (引数付き) を継承に追加しない方法を選択するのか興味があります。私の見解では、構造に応じて多くのコードを繰り返すことにより、DRY の原則に違反しています。私はほとんど調査を行いませんでした - PHP、Ruby、Python はコンストラクターを継承します。Java、C#、C++ ではありません。C++0x には、継承を明示的に定義する新しい機能があります。

プログラマーがコンストラクターを継承せず、明示的にコンストラクターを何度も記述しないという利点はありますか?

4

4 に答える 4

16

私は Mason の説明に満足できなかったので、少し調べてみました。

言語設計者がコンストラクターの継承を省略することを選択する主な理由が 2 つあることがわかりました。

最初の理由はバージョン管理です。新しいコンストラクターが基本クラスに導入された場合、または基本クラスでコンストラクターの署名が変更された場合、サブクラスはそれらの変更を継承し、予期しない副作用につながる可能性があります。

この問題は、多くの場合、さまざまなタイプのフレームワークで表面化します。多くの場合、フレームワークの API の一部は、拡張することが期待されるクラス、または実装する抽象クラスで構成されています。フレームワーク クラスでコンストラクターの署名が変更された場合、これらの変更を継承しますが、ほとんどの場合、意味がありません。これは、「壊れやすい基本クラス」の問題として知られています。

2 番目の理由は、より哲学的な側面にあり、1 番目の理由と密接に関連しています。コンストラクターは、インスタンスを構築するときに、そのクラスのコンシューマーとして、クラスが必要とするものを定義します。つまり、クラス自体のニーズに関するものです。これを、クラスの実際の動作を定義するメソッドと比較してください。それは、クラス何をするかについてです。それに基づいて、哲学的な議論は、継承は行動に関するものであり、ニーズに関するものではないということです。

これら 2 つの引数は、実際には同じコインの 2 つの側面です: ほぼ間違いなく、コンストラクターはクラスのニーズを定義するため、コンストラクターの継承は脆弱です。たとえば、基本クラスの作成者がすべてのニーズを予測できると期待するのは合理的ではありません。あなたの拡張クラスの。

または、少なくともその仮定を行うと (一部の言語が行うように)、基本クラスの有用性が大幅に制限されます。これは、拡張クラスに新しい動作を導入することしかできず、新しいニーズがまったくないためです。

私が思いついたもう 1 つのことは、完全性の問題です。これは個人的な好みの問題ですが、コンストラクターを継承する言語では、すべてのコンストラクターを学習して理解するには、基本クラスを何度も掘り下げる必要があることがよくあります。特定のクラスのインスタンスが構築されるさまざまな方法。

コンストラクターを継承できない場合、クラスの作成者は、クラスのインスタンスが構築されると予想されるすべての方法について明示する必要があります - これらのコンストラクターのいくつかが完全に自明なものになる場合でも、それらがクラス自体で定義されていることは、そのクラスの作成者がそれらを有用と見なし、別の作成者によって作成された可能性がある基本クラスのすべてのニーズを理解し、考慮する必要があることを示しています。

基本クラスのニーズが拡張クラスのニーズと一致するかどうかについて積極的に検討することなく、基本クラスのニーズをやみくもに継承するのではなく。

実際には、ニーズが一致していないことがわかりました。しかし、それらが整列したとしても、それらがどのように整列するかを考えることを強いられることは、より高いコード品質につながります。言語設計者がコンストラクターの非継承を選択するのは、それが哲学であると私は信じています。そこに書かれるコードとして。

于 2013-07-04T14:34:19.303 に答える
4

OO 言語は常に、スーパークラス コンストラクターと同じパラメーターを受け入れ、それらを使用してスーパークラスのコンストラクターを呼び出す既定のコンストラクターを提供できます。これを行うことを考えて言語に組み込む必要があることを除いて、これを行わない正当な理由はありません。

デフォルトのコンストラクターを提供しないということは、大量のボイラープレート コードを提供することを意味します。[GestureDetector][1]適切な例: 継承して、そのビルド メソッドの動作に小さな変更を加えたいと考えています。GestureDetector にはO(30)コンストラクター パラメーターがあり、これを宣言して単に super の呼び出しに渡す必要があります。これは、私が解決しようとしている問題には何の役にも立ちません。GestureDetector の再実装をコピー アンド ペーストすることをお勧めします (作業が減ります!)。これは、GestureDetector がその動作を適応させるための簡単なフックを提供していないためです。これには、フラッターの下位層を理解すること (これは重要です) と、はるかに多くの依存関係が変更されたときに壊れるコードを維持することが必要です (これは Flutter で頻繁に発生しています)。

@mindplay.dk の引数には同意しません。スーパークラス コンストラクターが変更された場合、明示的なコンストラクターが定義されているかどうかに関係なく、派生クラスに影響します。ほとんどの場合、単純に同じ名前のパラメーターを受け入れて渡すのが正しい考えです (これには新しい作業が含まれない可能性があるため、すばらしいことです)。そうでない場合は、新しいコンストラクターを定義すると、現在のソリューションと同じメンテナンス コストがかかります。

ボイラープレートを書かなければならないのは、言語の発達が不十分な兆候です。そして、Dart には確かに、ここやその他の点で改善する多くの方法があります。AST マクロを追加すると、この問題や他の多くの問題を回避できます。これらがファーストクラスの機能だったのはその時です(2020年、人々!)。

これにより、コンストラクターやその他の場所で、不足している独自の機能を実装できます。

@inherit_super_constructor 
class B extends GestureDetector{ ... }
class B extends A{
  @variadic_parameters   // Infers requirements from what is called
  B(*parent_positional, **parent_named, {this.extra}) : super(*parent_positional, **parent_named);

  String extra;
}
于 2020-04-08T20:19:23.100 に答える
2

おそらく Dart の設計者にその質問をする必要があると思いますが、継承ツリーからコンストラクターを省略する一般的な理由は、クラスに特定の構築時の操作が必要になる可能性があるためです。Java では (たとえば)、すべてのオブジェクトは、値がゼロのコンストラクターを持つ Object から継承します。

したがって、次のようなオブジェクトを作成したい場合:

public class Foo {
   private final String bar;

   public Foo(String barValue) {
       this.bar = barValue;
   }
}

誰かが親のゼロ パラメーター コンストラクターを呼び出したときに何が起こるかについて、いくつかの不確実性が残ります。

Foo bla = new Foo(); // runtime error or is bar null?

もちろん、すべての言語機能と同様に、この動作を指定する方法を考えることができますが、Java と dart の設計者がそれらのいずれも好まなかったことは明らかです。

最終的には、すべては哲学に帰着すると思います: php、ruby などはタイピングを減らす方向に進みますが、Java、C#、dart などはタイピングを増やしてスペースを減らす傾向があります。混乱のために。

于 2013-03-27T22:38:24.250 に答える