24

オープンクローズの原則は、「ソフトウェアエンティティ(クラス、モジュール、関数など)は拡張のためにオープンである必要がありますが、変更のためにクローズされるべきである」と述べています。

ただし、彼の有名な著書「Effective Java」のジョシュア・ブロックは、「継承のための設計と文書化、またはそれを禁止する」というアドバイスを提供し、プログラマーに「final」修飾子を使用してサブクラス化を禁止するように勧めています。

これらの2つの原則は明らかに互いに矛盾していると思います(私は間違っていますか?)。コードを書くとき、どの原則に従いますか、そしてその理由は何ですか?クラスを開いたままにしますか、それらの一部(どのクラスですか?)の継承を禁止しますか、または可能な限り最後の修飾子を使用しますか?

4

7 に答える 7

28

率直に言って、オープン/クローズドの原則は、そうでないよりも時代錯誤だと思います。これは、すべてが他のものから継承する必要があり、すべてがサブクラス化可能である必要があるという原則に基づいてOOフレームワークが構築された80年代と90年代に由来します。

これは、MFCやJavaSwingなどの時代のUIフレームワークで最も一般的でした。Swingでは、(iirc)ボタンがチェックボックスを拡張する(またはその逆)というばかげた継承があり、それらの1つに使用されていない動作を与えます(チェックボックスのsetDisabled()呼び出しだと思います)。なぜ彼らは祖先を共有するのですか?まあ、彼らにはいくつかの共通の方法があった以外の理由はありません。

最近の構成は継承よりも好まれています。Javaはデフォルトで継承を許可していましたが、.Netはデフォルトで継承を禁止する(より現代的な)アプローチを採用しました。これはより正しいと思います(そしてJosh Blochの原則とより一致しています)。

DI / IoCは、さらに構成を主張しました。

Josh Blochはまた、継承がカプセル化を破ることを指摘し、その理由のいくつかの良い例を示しています。また、クラスを拡張するのではなく、委任によって行われた場合、Javaコレクションの動作の変更がより一貫していることも実証されています。

個人的には、最近の継承は実装の詳細に過ぎないと考えています。

于 2009-03-18T12:17:49.433 に答える
9

2つのステートメントが互いに矛盾しているとは思いません。タイプは拡張のために開いていても、継承のために閉じていてもかまいません。

これを行う1つの方法は、依存性注入を採用することです。独自のヘルパータイプのインスタンスを作成する代わりに、タイプは作成時にこれらを提供することができます。これにより、タイプ自体を変更せずに(つまり、変更のために閉じる)、タイプのパーツを変更(つまり、拡張のために開く)することができます。

于 2009-03-18T12:13:58.380 に答える
7

オープンクローズの原則(拡張のためにオープン、変更のためにクローズ)では、最終的な修飾子を引き続き使用できます。これが1つの例です:

public final class ClosedClass {

    private IMyExtension myExtension;

    public ClosedClass(IMyExtension myExtension)
    {
        this.myExtension = myExtension;
    }

    // methods that use the IMyExtension object

}

public interface IMyExtension {
    public void doStuff();
}

はクラス内での変更のClosedClassために閉じられていますが、別のクラスを介した拡張のために開かれています。この場合、IMyExtensionインターフェースを実装するものであれば何でもかまいません。このトリックは、閉じたクラスに別のクラスを供給しているため、この場合はコンストラクターを介して、依存性注入のバリエーションです。拡張機能はそうするinterfaceことはできませんfinalが、その実装クラスはそうすることができます。

sealedクラスでfinalを使用してJavaでクラスを閉じるのは、C#で使用するのと似ています。.NET側でも同様の議論があります。

于 2009-03-18T12:15:10.407 に答える
5

私は Joshua Bloch を大いに尊敬しており、 Effective JavaはほぼJava のバイブルだと考えています。privateしかし、アクセスを自動的にデフォルトにすることはしばしば間違いだと思います。protected少なくともクラスを拡張することでアクセスできるように、デフォルトで作成する傾向があります。これは主にコンポーネントの単体テストの必要性から生まれましたが、クラスのデフォルトの動作をオーバーライドするのにも便利だと思います。自分の会社のコードベースで作業していて、作成者がすべてを「隠す」ことを選択したため、ソースをコピーして変更する必要がある場合は、非常に面倒です。それが私の力の範囲内であればprotected、重複を避けるためにアクセスを変更するよう働きかけますが、これは私見よりもはるかに悪いことです。

また、Bloch のバックグラウンドは、非常に 公開されている基盤 API ライブラリの設計にあるということも覚えておいてください。そのようなコードを「正しく」取得するためのハードルは非常に高く設定する必要があるため、実際に作成するほとんどのコードと同じ状況ではない可能性があります。JRE 自体などの重要なライブラリは、言語が乱用されないようにするために、より制限が厳しくなる傾向があります。JRE で廃止されたすべての API を参照してください。それらを変更または削除することはほとんど不可能です。あなたのコードベースはおそらく固定されていないので、最初に間違いを犯したことが判明した場合は、問題を修正する機会があります.

于 2009-03-18T12:20:56.273 に答える
5

現在、定型文の一部としてほとんど反射的に、デフォルトで final 修飾子を使用しています。特定のメソッドが、現在見ているコードで見られるように常に機能することがわかっていると、物事の推論が容易になります。

もちろん、クラス階層がまさにあなたが望むものである場合もあり、そのような場合にクラス階層を使用しないのはばかげています。ただし、2 つ以上のレベルの階層や、非抽象クラスがさらにサブクラス化されている階層には注意してください。クラスは、abstract または final のいずれかである必要があります。

ほとんどの場合、コンポジションを使用する方法が適しています。すべての一般的な機構を 1 つのクラスに入れ、さまざまなケースをさまざまなクラスに入れ、インスタンスを合成して全体が機能するようにします。

これを「依存性注入」、「戦略パターン」、「訪問者パターン」などと呼ぶことができますが、要約すると、繰り返しを避けるために継承ではなく構成を使用しています。

于 2009-03-18T12:32:38.680 に答える
3

2つのステートメント

ソフトウェア エンティティ (クラス、モジュール、関数など) は、拡張用に開いている必要がありますが、変更用には閉じている必要があります。

継承のための設計と文書化、または継承を禁止します。

互いに直接矛盾するものではありません。(Bloch のアドバイスに従って) 設計と文書化を行う限り、オープン/クローズの原則に従うことができます。

ブロッホは、作成する各クラスで継承を許可または禁止することを明示的に選択する必要があるだけで、final 修飾子を使用して継承を禁止することを好むべきだと述べているとは思いません。彼のアドバイスは、コンパイラのデフォルトの動作を単に受け入れるのではなく、よく考えて自分で決定するべきだというものです。

于 2009-03-18T13:18:18.553 に答える
2

私は、最初に提示されたオープン/クローズの原則が、依存関係の注入によって最終クラスを拡張できるという解釈を可能にしているとは思いません。

私の理解では、原則は、本番環境に置かれたコードへの直接の変更を許可しないことであり、機能の変更を許可しながらそれを達成する方法は、実装の継承を使用することです。

最初の答えで指摘されているように、これには歴史的なルーツがあります。数十年前、継承は有利であり、開発者のテストは前代未聞であり、コードベースの再コンパイルはしばしば時間がかかりすぎました。

また、C++ では、クラスの実装の詳細 (特にプライベート フィールド) が ".h" ヘッダー ファイルで一般的に公開されていたため、プログラマーがそれを変更する必要がある場合、すべてのクライアントで再コンパイルが必要になることを考慮してください。これは、Java や C# などの最新の言語には当てはまらないことに注意してください。その上、当時の開発者は、オンザフライで依存関係の分析を実行できる洗練された IDE を期待できず、頻繁な完全な再構築の必要性を回避できたとは思いません。

私自身の経験では、正反対のことをすることを好みますfinal。「デフォルトでは、クラスは拡張 ( ) に対して閉じている必要がありますが、変更に対しては開いています」。考えてみてください: 今日、私たちはバージョン管理(クラスの以前のバージョンの復元/比較を容易にする)、リファクタリング(デザインを改善するため、または新しい機能を導入するための前奏曲としてコードを変更することを奨励します)、および開発者などのプラクティスを好んでいます。 testingは、既存のコードを変更する際のセーフティ ネットを提供します。

于 2009-06-27T22:50:41.690 に答える