クラスが継承されないようにする理由は何ですか? (例: ac# クラスで seal を使用) 今のところ、思いつきません。
14 に答える
代用的に拡張されるクラスを作成するのは非常に難しく、作成したものを将来のユーザーがどのように拡張したいかを正確に予測する必要があるためです。
クラスを封印すると、より堅牢な構成を使用するように強制されます。
インターフェイスについてまだ確信が持てず、現在のインターフェイスに依存する他のコードが必要ない場合はどうですか? 【思いつきですが、他の理由も気になります!】
編集:少しグーグルすると、次のようになりました: http://codebetter.com/blogs/patricksmacchia/archive/2008/01/05/rambling-on-the-sealed-keyword.aspx
引用:
シールされたクラスがシールされていないクラスより優れている理由は 3 つあります。
- バージョニング: クラスが最初に封印されている場合、将来、互換性を損なうことなく非封印に変更できます。(…)
- パフォーマンス: (…) JIT コンパイラーがシール型を使用した仮想メソッドの呼び出しを検出した場合、JIT コンパイラーはメソッドを非仮想的に呼び出すことにより、より効率的なコードを生成できます。(…)
- セキュリティと予測可能性: クラスは自身の状態を保護し、それ自体が破損することを許さない必要があります。クラスがアンシールされると、フィールドを内部的に操作するデータ フィールドまたはメソッドがアクセス可能でプライベートでない場合、派生クラスは基本クラスの状態にアクセスして操作できます。(…)
「Code Complete」からこのメッセージをお伝えしたいと思います。
継承 (サブクラス) は、複雑さを管理するという、プログラマーとしての主要な技術的義務に反する傾向があります。複雑さを制御するために、継承に対して強い偏見を維持する必要があります。
継承の唯一の正当な使用法は、たとえば、Shape から継承して Circle を派生させる場合のように、基本クラスの特定のケースを定義することです。これを確認するには、反対方向の関係を見てください: Shape は Circle の一般化ですか? 答えが「はい」の場合、継承を使用しても問題ありません。
そのため、その動作を特殊化する特定のケースが存在しないクラスがある場合は、封印する必要があります。
また、LSP (Liskov Substitution Principle) により、基本クラスが期待される派生クラスを使用できますが、これは実際には継承の使用による最大の影響を課します: 基本クラスを使用するコードには継承されたクラスが与えられる場合があり、それでも期待どおりに動作する必要があります。 . サブクラスの明確な必要性がない場合に外部コードを保護するために、クラスを封印すると、クライアントはその動作が変更されないことを信頼できます。それ以外の場合は、サブクラスで動作が変更される可能性があることを想定して、外部コードを明示的に設計する必要があります。
より具体的な例は、Singleton パターンです。「シングルトン性」を破ることができないように、シングルトンを封印する必要があります。
オブジェクト指向の観点から、クラスを封印すると、コメントを必要とせずに作成者の意図が明確に文書化されます。私がクラスを封印するとき、私はこのクラスが特定の知識または特定のサービスをカプセル化するように設計されていると言おうとしています。これは、さらに拡張またはサブクラス化することを意図したものではありません。
これは、テンプレートメソッドのデザインパターンによく合います。「このサービスを実行します」というインターフェイスがあります。次に、そのインターフェイスを実装するクラスがあります。しかし、そのサービスの実行が、基本クラスが認識していない(そして認識してはならない)コンテキストに依存している場合はどうなるでしょうか。何が起こるかというと、基本クラスは保護またはプライベートの仮想メソッドを提供し、これらの仮想メソッドは、基本クラスが知らない、または知ることができない情報またはアクションを提供するサブクラスのフックです。一方、基本クラスには、すべての子クラスに共通のコードを含めることができます。これらのサブクラスは、サービスの唯一の具体的な実装を実現することを目的としているため、封印されます。
これらのサブクラスは、それらを強化するためにさらにサブクラス化する必要があるという議論をすることができますか?そのサブクラスがそもそも仕事を成し遂げることができなかったなら、それは基本クラスから派生するべきではなかったので、私はノーと言います。それが気に入らない場合は、元のインターフェイスがあります。独自の実装クラスを作成してください。
これらのサブクラスを封印すると、深いレベルの継承が妨げられます。これは、GUIフレームワークではうまく機能しますが、ビジネスロジックレイヤーではうまく機能しません。
これはコードには当てはまらないかもしれませんが、.NET フレームワーク内の多くのクラスは意図的に封印されているため、誰もサブクラスを作成しようとしません。
内部が複雑で、特定のものを非常に具体的に制御する必要がある特定の状況があるため、設計者はクラスを継承してはいけないと判断し、間違った方法で何かを使用して誤って機能を壊すことはありません。
@jjnguy
別のユーザーが、クラスをサブクラス化してコードを再利用したい場合があります。これを止める理由が見当たりません。
彼らが私のクラスの機能を使用したい場合、封じ込めによってそれを達成することができ、その結果、脆弱なコードがはるかに少なくなります。
構成は見過ごされがちです。多くの場合、人々は継承の時流に乗りたがります。彼らはすべきではありません!代用は難しい。デフォルトでコンポジション。今後ともよろしくお願いいたします。
私は jjnguy に同意します... クラスを封印する理由はほとんどないと思います。逆に、クラスを延長したいのに、封印されていて延長できないという状況に何度か遭遇しました。
完璧な例として、私は最近、memcached ツールの機能をラップする小さなパッケージ (C# ではなく Java ですが、同じ原則) を作成していました。テストで、使用していた memcached クライアント API をモックできるインターフェイスが必要でした。また、必要に応じてクライアントを切り替えることもできました (memcached ホームページには 2 つのクライアントがリストされています)。さらに、必要性や要望が生じた場合 (memcached サーバーが何らかの理由でダウンした場合などに、代わりにローカル キャッシュの実装とホット スワップできる可能性がある場合) に、機能を完全に置き換える機会が欲しかったのです。
クライアント API とやり取りするための最小限のインターフェイスを公開しました。クライアント API クラスを拡張してから、新しいインターフェイスに実装句を追加するだけでよかったと思います。実際のインターフェイスと一致するインターフェイスに含まれていたメソッドは、それ以上の詳細を必要としないため、明示的に実装する必要はありません。ただし、クラスは封印されていたため、代わりにこのクラスへの内部参照への呼び出しをプロキシする必要がありました。その結果、正当な理由もなく、より多くの作業とより多くのコードが必要になります。
とはいえ、クラスを封印したい場合もあると思います...そして私が考えることができる最善の方法は、直接呼び出すAPIですが、クライアントが実装できるようにすることです. たとえば、ゲームに対してプログラミングできるゲーム...クラスが封印されていない場合、機能を追加しているプレーヤーが API を有利に利用できる可能性があります。ただし、これは非常に狭いケースであり、コードベースを完全に制御できる場合はいつでも、クラスを封印する理由はほとんどないと思います。
これが、私が Ruby プログラミング言語を本当に気に入っている理由の 1 つです... コアクラスでさえ、拡張するだけでなく、機能をクラス自体に動的に追加および変更するためにオープンです! これはモンキーパッチと呼ばれ、悪用されると悪夢になる可能性がありますが、遊ぶのはとても楽しいです!
クライアントAPIと対話するための最小限のインターフェイスを公開しましたが、クライアントAPIクラスを拡張してから、新しいインターフェイスでimplements句を追加するだけですばらしいでしょう。実際のインターフェースと一致するインターフェースにあるメソッドは、それ以上の詳細を必要としないため、明示的に実装する必要はありません。ただし、クラスは封印されていたため、代わりにこのクラスへの内部参照へのプロキシ呼び出しを行う必要がありました。結果:本当の理由もなく、より多くの作業とより多くのコードが得られます。
理由があります。コードは、memcachedインターフェースへの変更からある程度隔離されています。
パフォーマンス:(…)JITコンパイラーが、封印された型を使用した仮想メソッドの呼び出しを検出した場合、JITコンパイラーは、メソッドを非仮想的に呼び出すことにより、より効率的なコードを生成できます。(…)
それは確かに大きな理由です。したがって、パフォーマンスが重要なクラスの場合sealed
、友人は理にかなっています。
私がこれまでに言及した他のすべての理由は、「誰も私のクラスに触れない!」に要約されます。誰かがその内部を誤解するかもしれないと心配しているなら、あなたはそれを文書化するのに貧弱な仕事をしました。クラスに追加するのに役立つものが何もないことや、考えられるすべてのユースケースをすでに知っていることを、おそらく知ることはできません。あなたが正しく、他の開発者があなたのクラスを使用して問題を解決するべきではなかったとしても、キーワードを使用することはそのような間違いを防ぐための優れた方法ではありません。ドキュメントはです。彼らがドキュメントを無視した場合、彼らの損失。
さまざまな理由から、派生クラスではなくクラスへの参照を常に渡したいためです
。コードの他の部分にある不変条件
ii. セキュリティ
など
また、下位互換性に関しては安全な賭けであるため、封印されていないリリースの場合、継承のためにそのクラスを閉じることはできません。
または、クラスが公開するインターフェイスをテストして、他のユーザーがそのインターフェイスから継承できることを確認する十分な時間がなかった可能性があります。
あるいは、サブクラスを持っても意味がない (今はわかります) かもしれません。
または、人々がサブクラス化しようとして、すべての核心的な詳細を取得することができなかったときに、バグレポートが必要ない場合-サポートコストを削減します.
クラス インターフェイスが継承されることを意図していない場合があります。パブリック インターフェイスは仮想的ではなく、誰かが配置されている機能をオーバーライドできますが、それは間違っているだけです。はい、一般に、パブリック インターフェイスをオーバーライドするべきではありませんが、クラスを継承不可にすることでオーバーライドしないようにすることができます。
私が今思いつく例は、.Net のディープ クローンを持つカスタマイズされた包含クラスです。それらから継承すると、ディープ クローン機能が失われます。[この例についてはちょっとあいまいです。IClonable を使用してからしばらく経ちます]データ永続化レイヤーは通常、多くの継承が必要な場所ではありません。
ほとんどの回答 (抽象化されている場合) は、シール/ファイナライズされたクラスは、他のプログラマーを潜在的なミスから保護するためのツールであると述べています。意味のある保護と無意味な制限の間には、あいまいな境界線があります。しかし、プログラマーがプログラムを理解することが期待されている限り、プログラマーがクラスの一部を再利用することを制限する理由はほとんどないと思います。ほとんどのクラスについて話します。しかし、それはすべてオブジェクトに関するものです!
最初の投稿で、DrPizza は、継承可能なクラスを設計することは、可能な拡張を予測することを意味すると主張しています。適切に拡張される可能性が高い場合にのみ、クラスを継承可能にする必要があると考えているのは正しいですか? 最も抽象的なクラスからソフトウェアを設計することに慣れているかのように見えます。設計時にどのように考えるかについて簡単に説明させてください。
非常に具体的なオブジェクトから始めて、それらに共通する特性と機能を見つけ、それらの特定のオブジェクトのスーパークラスに抽象化します。これは、コードの重複を減らす方法です。
フレームワークなどの特定の製品を開発しない限り、他の (仮想) コードではなく、自分のコードを気にする必要があります。他の人が私のコードを再利用すると便利だと思うかもしれないという事実は、私の主な目標ではなく、素晴らしいボーナスです。そうすることを決定した場合、拡張機能の有効性を保証するのは彼らの責任です。これはチーム全体に適用されます。事前の設計は、生産性にとって非常に重要です。
私の考えに戻ります。オブジェクトは、サブタイプの可能性のある機能ではなく、主に目的を果たす必要があります。あなたの目標は、与えられた問題を解決することです。オブジェクト指向言語は、多くの問題 (またはそのサブ問題の可能性が高い) が類似しているため、既存のコードを使用してさらなる開発を加速できるという事実を利用しています。
クラスを封印すると、製品を実際に変更せずに既存のコードを利用できる可能性のある人々が、車輪の再発明を余儀なくされます。(これは私の論文の重要なアイデアです: クラスを継承してもクラスは変更されません! これは非常に平凡で明白なように見えますが、一般的に無視されています)。
人々は、自分の「オープンな」クラスが、その先祖を置き換えることができない何かにねじ曲げられるのではないかとしばしば恐れています。だから何?なぜあなたは気にする必要がありますか?悪いプログラマーによる悪いソフトウェアの作成を防ぐツールはありません。
継承可能なクラスを最終的に正しい設計方法として示しようとしているわけではありません。これは、継承可能なクラスに対する私の傾向の説明のようなものだと考えてください。それがプログラミングの美しさです。それぞれに長所と短所がある、事実上無限の正しいソリューションのセットです。あなたのコメントや議論は大歓迎です。
そして最後に、元の質問に対する私の答えです。クラスを完成させて、そのクラスを階層クラス ツリーのリーフと見なし、それが親ノードになる可能性はまったくないことを他の人に知らせます。(そして、誰かが実際に可能だと思うなら、私が間違っていたか、彼らが私を理解していないかのどちらかです).
クラスで重要なことのすべてがコードで簡単にアサートできるわけではありません。メソッドの継承とオーバーライドによって簡単に壊れてしまうセマンティクスと関係が存在する可能性があります。これを行うには、一度に 1 つのメソッドをオーバーライドするのが簡単です。クラス/オブジェクトを単一の意味のあるエンティティとして設計すると、誰かがやって来て、1つまたは2つのメソッドが「より良い」場合、害はないだろうと考えます。それは真実かもしれませんし、そうでないかもしれません。プライベートと非プライベート、または仮想と非仮想の間ですべてのメソッドを正しく分離できるかもしれませんが、それでも十分ではないかもしれません。また、すべてのクラスの継承を要求することは、元の開発者に、継承するクラスが物事を台無しにする可能性があるすべての方法を予測するという大きな追加の負担を課します。
私は完璧な解決策を知りません。継承を防ぐことには同情しますが、単体テストの妨げになるので、それも問題です。