さまざまなStack Overflowの質問や他の人のコードを読むとき、クラスを設計する方法の一般的なコンセンサスは閉じています。これは、デフォルトでJavaおよびC#ではすべてがプライベートであり、フィールドはfinalであり、一部のメソッドはfinalであり、 クラスはさらにfinalである場合もあります です。
この背後にある考え方は、実装の詳細を隠すことです。これは非常に正当な理由です。ただし、ほとんどのOOP言語および多態性でprotected
が存在するため、これは機能しません。
クラスに機能を追加または変更したいときはいつでも、私は通常、どこにでも配置されているプライベートおよびファイナルによって妨げられています。ここで実装の詳細が重要になります。結果を理解して、実装を拡張します。ただし、プライベートフィールドとファイナルフィールドおよびメソッドにアクセスできないため、3つのオプションがあります。
それらは良いオプションではありません。それをサポートする言語で書かれたプロジェクトでprotected
が使用されないのはなぜですか?一部のプロジェクトで、クラスからの継承が明示的に禁止されているのはなぜですか?
拡張時に適切に機能するようにクラスを設計する場合、特に拡張を行うプログラマーがクラスの動作方法を完全に理解していない場合は、かなりの労力がかかります。非公開のものをすべて取り込んで公開(または保護)して、それを「オープン」と呼ぶことはできません。他の誰かに変数の値の変更を許可する場合は、考えられるすべての値がクラスにどのように影響するかを考慮する必要があります。 (変数をnullに設定した場合はどうなりますか?空の配列?負の数値?)他の人がメソッドを呼び出せるようにする場合も同様です。慎重に考えます。
したがって、クラスをオープンにしてはいけないということはそれほど多くありませんが、クラスをオープンにしておく価値がない場合もあります。
もちろん、ライブラリの作者が怠惰だった可能性もあります。あなたが話しているライブラリに依存します。 :-)
デフォルトですべてを非公開にすることは厳しいように聞こえますが、反対側から見てください。すべてがデフォルトで非公開である場合、何かを公開する(または保護することはほぼ同じです)ことは意識的な選択であるはずです。それは、クラスの使用方法に関する、消費者であるクラス作成者の契約です。これはあなたにとっても便利です。インターフェイスが変更されない限り、作成者はクラスの内部動作を自由に変更できます。また、クラスのどの部分に依存できるか、どの部分が変更される可能性があるかを正確に把握できます。
根底にある考え方は「疎結合」(「狭いインターフェース」とも呼ばれます)です。その価値は、複雑さを抑えることにあります。コンポーネントが相互作用する方法の数を減らすことにより、コンポーネント間の相互依存の量も減少します。相互依存性は、保守と変更管理に関して、最も複雑な種類の1つです。
適切に設計されたライブラリでは、継承を通じて拡張する価値のあるクラスは、保護されたメンバーとパブリックメンバーを適切な場所に配置し、他のすべてを非表示にします。
not privateであるものはすべて、多かれ少なかれ、クラスの将来のすべてのバージョンで変更されていない動作で存在するであると想定されています。実際には、ドキュメント化されているかどうかにかかわらず、APIの一部と見なすことができます。したがって、あまりにも多くの詳細を公開すると、後で互換性の問題が発生する可能性があります。
「最終」応答について。 「密封された」クラス、1つの典型的なケースは不変クラスです。フレームワークの多くの部分は、文字列が不変であることに依存しています。 Stringクラスがfinalでない場合、不変のStringクラスの代わりに使用すると、他の多くのクラスであらゆる種類のバグを引き起こす可変のStringサブクラスを作成するのは簡単です(魅力的ですら)。
OOでは、既存のコードに機能を追加する方法が2つあります。
1つ目は継承によるものです。クラスを受け取り、そこから派生します。ただし、継承は注意して使用する必要があります。基本と派生クラスの間にisA関係がある場合、主にパブリック継承を使用する必要があります(例:長方形はShape)です。代わりに、既存の実装を再利用するためのパブリック継承を回避する必要があります。基本的に、パブリック継承は、派生クラスが基本クラスと同じ(またはより大きな)インターフェイスを持つことを確認するために使用されるため、 Liskovの置換原則 を適用できます。
機能を追加するもう1つの方法は、委任または構成を使用することです。既存のクラスを内部オブジェクトとして使用する独自のクラスを作成し、実装作業の一部をそれに委任します。
あなたが使用しようとしているライブラリ内のすべてのファイナルの背後にあるアイデアが何であったかはわかりません。おそらくプログラマーは、クラスから新しいクラスを継承しないようにしたかったのでしょう。これは、コードを拡張するための最良の方法ではないためです。
オブジェクトの設計や拡張が意図されていない場合は、クラスをシールする必要があります(final、NotOverridableなど)。
ただし、適切なコード設計をすべて閉じるだけでは考慮しません。 SOLIDの「O」は、クラスが変更に対して「クローズ」される必要があるが、拡張に対して「オープン」である必要があることを示す「オープン-クローズドプリンシプル」用です。アイデアは、コードがあることですオブジェクト内で正常に動作します。機能を追加するには、そのコードを開いて、以前の動作を壊す可能性のある外科的な変更を加える必要はありません。代わりに、継承または依存性注入を使用して、クラスを「拡張」して追加の操作を実行できる必要があります事。
たとえば、「ConsoleWriter」クラスはテキストを取得してコンソールに出力する場合があります。これはうまくいきます。ただし、場合によっては、同じ出力をファイルに書き込む必要もあります。 ConsoleWriterのコードを開き、その外部インターフェイスを変更してパラメーター「WriteToFile」をメイン関数に追加し、追加のファイル書き込みコードをコンソール書き込みコードの横に配置することは、一般的に悪いことと見なされます。
代わりに、2つのことの1つを行うことができます。ConsoleWriterから派生してConsoleAndFileWriterを作成し、Write()メソッドを拡張して最初に基本実装を呼び出し、次にファイルに書き込むこともできます。または、ConsoleWriterからインターフェイスIWriterを抽出し、そのインターフェイスを再実装して2つの新しいクラスを作成することもできます。 FileWriter、および他のIWriterを指定でき、指定されたすべてのライターにそのメソッドに対して行われたすべての呼び出しを「ブロードキャスト」する「MultiWriter」。どちらを選択するかは、必要なものによって異なります。単純な派生のほうが単純ですが、最終的にメッセージをネットワーククライアント、3つのファイル、2つの名前付きパイプ、およびコンソールに送信する必要があるという先見の明がない場合は、先に進んで問題を解決し、インターフェイスを抽出して、 「Yアダプター」;バックエンドで時間を節約できます。
ここで、Write()関数が仮想として宣言されたことがない(または封印された)場合、ソースコードを制御しないと問題が発生します。時々そうする場合でも。これは一般的に悪い位置にあり、クローズドソースまたは制限付きソースのAPIのユーザーがそれが発生したときに終わりがないことにイライラします。しかし、Write()、またはクラス全体のConsoleWriterが封印される正当な理由があります。保護されたフィールドに機密情報が含まれている可能性があります(その情報を提供するために基本クラスから順にオーバーライドされます)。検証が不十分な場合があります。 APIを作成するときは、コードを使用するプログラマーが、最終的なシステムの平均的な「エンドユーザー」よりも賢くも、慈悲深いものでもないと想定する必要があります。そうすれば、決してがっかりすることはありません。 APIを拡張するためにコンシューマーが実行できるすべてのことから時間をかけて検証するか、Fort KnoxのようにAPIをロックして、期待どおりの機能しか実行できないようにします。
彼らはもっとよく知らないからです。
元の作者はおそらく、混乱した複雑なC++の世界で発生したSOLIDの原理の誤解に固執しています。
Ruby、python、Perlの世界には、ここでの回答が封印の理由であると主張する問題がないことに気付くでしょう。動的タイピングと直交していることに注意してください。ほとんどの(すべての?)言語でアクセス修飾子を使用するのは簡単です。 C++フィールドは、他の型にキャストすることで変更できます(C++はより弱い)。 JavaおよびC#はリフレクションを使用できます。本当に必要な場合を除き、アクセス修飾子を使用すると、そうすることができなくなります。
クラスを封印し、メンバーにプライベートのマークを付けることは、単純なことは単純でなければならず、難しいことは可能であるべきであるという原則に違反します。突然、シンプルなはずのものが簡単になりません。
原作者の視点を理解することをお勧めします。その多くは、現実の世界で絶対的な成功を収めたことのないカプセル化の学術的アイデアからのものです。フレームワークやライブラリを見たことがありません。どこかで開発者がそれがわずかに異なって機能することを望んでおらず、変更する正当な理由もありませんでした。メンバーを封印してプライベートにした元のソフトウェア開発者を悩ませた可能性が2つあります。
企業の枠組みでは、#2がおそらくそうだと思います。これらのC++、Javaおよび.NETフレームワークは「実行」する必要があり、特定のガイドラインに従う必要があります。これらのガイドラインは、型が型階層の一部として明示的に設計されていない限り、通常はシールされた型を意味します。他の人が使うのに役立つかもしれない多くのもののためのプライベートメンバー..しかし、それらはそのクラスに直接関係しないので、それらは公開されません。新しいタイプを抽出することは、サポート、ドキュメント化などにコストがかかりすぎるでしょう...
アクセス修飾子の背後にある全体的なアイデアは、プログラマーが自分自身から保護されるべきであるということです。 「Cプログラミングは、自分の足で撃つことができるので悪いです。」プログラマとして同意するのは哲学ではありません。
私はpythonの名前マングリングアプローチを非常に好みます。必要に応じて、プライベートを簡単に(リフレクションよりもはるかに簡単に)置き換えることができます。それについての素晴らしい記事がここにあります: http://bytebaker.com/2009/03/31/python-properties-vs-Java-access-modifiers/
Rubyのプライベート修飾子は、実際にはC#で保護されているようなものであり、C#修飾子としてのプライベートはありません。保護されているとは少し異なります。ここに素晴らしい説明があります: http://www.Ruby-lang.org/en/documentation/Ruby-from-other-languages/
覚えておいてください、あなたの静的言語は、その言語の過去に書かれたコードの時代遅れのスタイルに準拠する必要はありません。
それはおそらくcプログラミングにoo言語を使用しているすべての人々からのものだと思います。私はあなたを見ています、ジャバ。ハンマーがオブジェクトのような形をしているが、手続きシステムが必要な場合、および/またはクラスを理解しない場合、プロジェクトは次のことを行う必要がありますロックダウンされます。
基本的に、oo言語で記述する場合、プロジェクトは拡張機能を含むooパラダイムに従う必要があります。もちろん、誰かが別のパラダイムでライブラリを書いた場合、おそらくそれを拡張することはできません。
編集:クラスはオープンになるように設計する必要があると思います。いくつかの制限がありますが、継承のために閉じるべきではありません。
理由:「最終クラス」または「密閉クラス」は奇妙に思えます。私は自分のクラスの1つを「最終」(「封印」)としてマークする必要はありません。後でそのクラスを継承する必要がある場合があるからです。
私はクラス付きのサードパーティライブラリ(ほとんどの視覚的コントロール)を購入/ダウンロードしましたが、それらがコーディングされたプログラミング言語でサポートされている場合でも、「最終」を使用したライブラリはどれもありません。これらのクラスの拡張を終了したため、ソースコードなしでライブラリをコンパイルしたとしても。
時々、私は時々「封印された」クラスを扱う必要があり、同様のメンバーを持つ、指定されたクラスを含む新しいクラス「ラッパー」の作成を終了しました。
class MyBaseWrapper {
protected FinalClass _FinalObject;
public virtual DoSomething()
{
// these method cannot be overriden,
// its from a "final" class
// the wrapper method can be open and virtual:
FinalObject.DoSomething();
}
} // class MyBaseWrapper
class MyBaseWrapper: MyBaseWrapper {
public override DoSomething()
{
// the wrapper method allows "overriding",
// a method from a "final" class:
DoSomethingNewBefore();
FinalObject.DoSomething();
DoSomethingNewAfter();
}
} // class MyBaseWrapper
スコープ分類子は、継承を制限せずに一部の機能を「シール」することを可能にします。
Structured Progrから切り替えたとき。 OOPでは、privateクラスメンバーの使用を開始しましたが、多くのプロジェクトの後、protectedの使用を終了し、最終的にそれらのプロパティまたはメソッドをpublic(必要な場合)。
追伸私はC#のキーワードとして「final」を好きではありません。継承メタファーに従って、Javaのような「sealed」または「sterile」を使用します。さらに、「最終」は、いくつかのコンテキストで使用されるあいまいな単語です。