コーディングスタイルガイドを作成するために、ソフトウェア設計の最終的な方法はどのように判断すべきですか?
最後に、サブクラス化できるクラスがメソッドを提供し、そのクラスがサブクラスが特定のメソッドを変更しないようにする特別な構文を使用するというオブジェクト指向の意味を意味します。
Javaのような言語では、メソッドをfinalにする、静的メソッドを使用する(該当する場合)など、動作の変更を防ぐ他の方法があります。
例として、このスタイルガイドには「最終的なメソッドまたはクラスはありません」というセクションがあります https://doc.nuxeo.com/corg/Java-code-style/
最終的なメソッドやクラスはありません
これは再利用性を妨げます。 Nuxeoはプラットフォームであり、何かをサブクラス化することがいつ役立つかはわかりません。
プライベートメソッドまたはパッケージプライベートメソッドまたはフィールドはありません。
これは、上記と同じ理由で、再利用性を妨げます。
一方、Guavaには https://guava.dev/releases/19.0/api/docs/com/google/common/collect/AbstractIterator.html のような最終的なメソッドを備えたクラスがあります。
JDK(Java)には、ArrayList、AbstractListなどの最終メソッドがほとんどないクラス、HashMapなどの最終メソッドがいくつかあるクラス、AbstractPipelineなどの最終メソッドが多いクラスがあります。
これはオープンクローズの原則( オープン/クローズの原則を明確にする )に関連すると言う人もいますが、そのトピックに関する記事では通常、最終的な方法については触れていません。
別の見方をすれば、これは継承の設計に関する議論よりも構成に関連しているということです( 継承よりも構成を好むのはなぜですか? )。これは、メソッドをfinalにするかどうかを検討するときに、継承を介して機能を提供することになるためです。
やや関連:
私は2004年のEric Lippertのブログ投稿で提示された議論 なぜフレームワーククラスの多くが封印されているのですか? は「最終メソッド」に適用されます(Javaその用語)も。
Final以外のメソッドBを呼び出すメソッドA(フレームワーク内)を作成するたびに、Aは、Bが元々行っていた処理に依存することができないため、Aは、Bがfinalである場合よりもはるかに堅牢に設計する必要があります。 。たとえば、実装Aは(Bを呼び出すとき)例外処理により多くの労力を費やす必要がある場合があり、Bの正確な動作とそれをオーバーライドするときに適用される制約をより正確に文書化する必要があり、Aはさまざまなオーバーライドでより徹底的にテストする必要がありますBのバリアント。さらに、AとBの間の責任の正確な分布について、より多くの検討が必要です。
実際、フレームワークの抽象クラスでは、オーバーライド可能なメソッドが内部で使用される方法は、そのフレームワークのAPIの一部になります(@wcharginによるコメントの例を参照)。フレームワークが世界に公開されると、これらのメソッドのセマンティクスを変更することが非常に難しくなります。
したがって、これはトレードオフになります。Bをfinalにすることで、メソッドAの正確でテスト済みの信頼できる実装を作成しやすくなり、後でフレームワーク内でAとBをリファクタリングしやすくなりますが、さらに難しくなります。フレームワークの実装ガイドでnothingをfinalにすることを支持している場合、そのソフトウェアの信頼性については非常に懐疑的です。
エリックの最後の段落を引用しましょう。これはここで完全に適用されます。
ここには明らかにトレードオフがあります。トレードオフは、開発者が古いオブジェクトをプロパティバッグとして処理できるようにすることで少し時間を節約できるようにすることと、適切に設計されたOOPtacularで十分な機能を備えた堅牢で安全な予測可能なテスト可能なフレームワークを開発することです。妥当な時間-そして私は後者に大きく傾くつもりです。あなたは何を知っているのですか?私たちが彼らに提供するフレームワークが中途半端で、もろく、安全でなく、十分にテストされていないために速度が低下すると、これらの同じ開発者はひどく不満を言うでしょう!
2014年のこの古い質問(およびそのトップの回答)は、ここでも優れた回答となる可能性があります。
C#では、メソッドはデフォルトで(Javaその用語の意味で)「最終」であり、virtual
キーワードを明示的に追加してオーバーライド可能にする必要があります。Javaでは、それは逆です:すべてのメソッドはデフォルトで「仮想」であり、これを防ぐためにfinal
としてマークする必要があります。
その前の質問へのトップの答えは、これらのアプローチの背後にあるさまざまな「思考の学校」を説明するためにアンダース・ハイルスバーグを引用しています。
彼が「学術的」(「いつかそれを上書きしたいかもしれないので、すべてが仮想的であるべきだ」と呼ぶ)の考えの学校、対.
「プラグマティック」な考え方の学校(「仮想化するものについては、十分に注意する必要があります。」)
最後に、後者の議論は私にとってより説得力があるように見えますが、YMMVです。
継承は強力なツールです。すべての強力なツールと同様に、誤用しやすい場合があります。したがって、いずれの場合も最終的なものかどうかを検討することが重要です。
引用されたスタイルガイドのように、すべてをオープンで拡張可能なものにすることで、サブクラスに対して可能な限り簡単なものにし、それらのオプションを可能な限り開いたままにすることを考えるのは魅力的です。これは理にかなっているようです(過度にロックダウンされたサードパーティクラスを拡張しようとしたことのある人なら誰でも知っているでしょう)。しかし、それはそれほど単純ではありません…
すべてをオープンで拡張可能なものにすることに関する主な問題の1つは、 脆弱な基本クラスの問題 として知られています。
2つのクラスが密結合であるほど、一方を変更すると他方が破壊される可能性が高くなります。そして、すべてのスーパークラスの内部に完全にアクセスできるサブクラスは、非常に密結合されています。スーパークラスに無害な変更を加えると、サブクラスがコンパイルされますが、正しく動作しません。 (特に、1つのスーパークラスメソッドが別のスーパークラスメソッドを呼び出すように、または呼び出しを停止するように変更された場合。)
両方のクラスが直接制御下にある場合、これは修正可能です。しかし、それらが異なるモジュールまたはライブラリのものである場合、特に異なる組織がそれらを担当している場合は、非常に深刻な問題になる可能性があります。
そのため、Joshua Blochは(非常に注目されている本の項目17で Effective Java ;第3版の項目19) デザインと継承のためのドキュメントまたはそれ以外の場合は禁止 。これらの問題のいくつか、およびそれらを回避するために実行するアクション(コンストラクターでオーバーライド可能なメソッドの呼び出しを回避すること、公開するフィールドとメソッドを慎重に検討すること、すべての自己呼び出しを含むそれらの動作を慎重に文書化することなど)パフォーマンス上の理由から賢明に選択されたヘルパーメソッド、クラスをテストするための複数のサブクラスの作成、クラスの存続期間中のドキュメント化へのコミット。 この問題の最善の解決策は、安全にサブクラス化されるように設計および文書化されていない方法でサブクラス化を禁止することです
(これが、KotlinがJavaのポリシーを逆にして、メソッドをデフォルトでfinalにする主な理由の1つであり、オーバーライド可能にするためにopen
キーワードを必要とします。)
したがって、拡張可能なクラスを作成するときは、継承について慎重に検討する価値があります。将来、いつでもより多くのものをオープンにすることができます。 はるかに逆に行くのは難しいです。