これはかなり基本的なOOの質問ですが、しばらくの間私を悩ませてきた質問です。
protected
を優先して、フィールドとメソッドに「private」可視性修飾子を使用しない傾向があります。
これは、クラスの拡張に関する特定のガイドラインを設定する場合を除いて、一般に、基本クラスと子クラスの間に実装を非表示にすることには何の役にも立たないためです(つまり、フレームワークで)。ほとんどの場合、自分または他のユーザーがクラスを拡張する方法を制限しようとすることは有益ではないと思います。
ただし、大多数の人にとって、非公開のフィールド/メソッドを定義する場合、通常はprivate
修飾子がデフォルトの選択肢です。
では、private
のユースケースを挙げていただけますか?常にプライベートを使用する主な理由はありますか?それとも、使いすぎだと思いますか?
OOPでは 継承よりも構成を優先する すべきであるというコンセンサスがいくつかあります。これにはいくつかの理由があります(興味があればグーグル)が、主な部分はそれです:
したがって、クラスを継承可能にすることを選択した場合は、すべての長所と短所を念頭に置いて、意識的に行う必要があります。
したがって、クラスを継承可能にせず、代わりに他の手段を使用してクラスが可能な限り柔軟であることを確認することをお勧めします。
これは、クラスの使用法が制御できない大規模なフレームワークではほとんど明らかです。あなた自身の小さなアプリの場合、これはそれほどに気付かないでしょうが、そうでない場合は遅かれ早かれそれ(デフォルトでの継承)があなたを後ろに噛みます注意してください。
代替案
構成とは、明示的な(完全に抽象的な)インターフェイス(仮想またはテンプレートベース)を介してカスタマイズ可能性を公開することを意味します。
したがって、仮想drive()関数を備えたVehicle基本クラス(価格の整数などの他のすべて)を使用する代わりに、Motorインターフェイスオブジェクトを取得するVehicleクラスと、そのMotorインターフェイスを使用できます。 drive()関数のみを公開します。これで、あらゆる種類のモーターをどこにでも追加して再利用できます(多かれ少なかれ:)。
メンバーがprotected
であるかprivate
であるかが重要になる状況は2つあります。
派生クラスがメンバーにアクセスできることでメリットが得られる現実的なシナリオを想像でき、基本クラスがその動作を変更することでメリットが得られるシナリオを想像できない場合、メンバーはprotected
[と仮定します。もちろん、公開すべきではないということ]。派生クラスがメンバーに直接アクセスすることで大きなメリットが得られるシナリオを想像できないが、基本クラスの将来のバージョンがそれを変更することでメリットが得られるシナリオを想像できる場合は、private
である必要があります。これらのケースは非常に明確で簡単です。
基本クラスがメンバーを変更することで利益を得るというもっともらしいシナリオがない場合は、それをprotected
にすることに傾倒することをお勧めします。 「YAGNI」(You Ai n't Gonna Need It)の原則はprivate
を支持すると言う人もいますが、私は同意しません。他の人がクラスを継承することを期待している場合、メンバーをプライベートにすることは「YAGNI」ではなく「HAGNI」(彼はそれを必要としない)を想定しています。 「あなた」がクラスの将来のバージョンでアイテムの動作を変更する必要がない限り、「あなた」はそれがprivate
である必要はありません。対照的に、多くの場合、クラスのコンシューマーが何を必要としているかを予測する方法はありません。 protected
はどちらの決定にも実際には当てはまらないため、最初にメンバーを変更することでメリットが得られる方法を特定しようとせずに、メンバーをYAGNI
にする必要があるという意味ではありません。 YAGNIは、将来のニーズに遭遇した場合に対処できる場合に適用されるため、今すぐ対処する必要はありません。他のプログラマーに与えられるクラスのメンバーを作成するという決定は、private
またはprotected
が、どのタイプの潜在的な将来のニーズに対応するかについての決定を意味し、他を提供します。
両方のシナリオが妥当な場合もあります。その場合、2つのクラスを提供すると役立つ場合があります。1つは問題のメンバーを公開し、派生クラスは公開しないクラスです(派生クラスがメンバーを非表示にするための標準的な慣用句はありません)。親から継承されますが、同じ名前でコンパイル可能な機能がなく、Obsolete
属性でマークされた新しいメンバーを宣言すると、その効果があります)。関連するトレードオフの例として、List<T>
を検討してください。型がバッキング配列を保護されたメンバーとして公開した場合、T CompareExchangeItem(index, T T newValue, T oldvalue)
を返すメンバーInterlocked.CompareExchange(_backingArray[index], newValue, oldValue)
を含む派生型CompareExchangeableList<T> where T:Class
を定義することが可能です。このようなタイプは、List<T>
を予期する任意のコードで使用できますが、インスタンスがCompareExchangeableList<T>
であることを認識しているコードでは、CompareExchangeItem
を使用できます。残念ながら、List<T>
はバッキング配列を派生クラスに公開しないため、リストアイテムでCompareExchange
を許可するタイプを定義することはできませんが、List<T>
を期待するコードでは引き続き使用できます。
それでも、それはバッキングアレイの公開が完全にコストなしであったことを意味するものではありません。 List<T>
の既存の実装はすべて単一のバッキング配列を使用しますが、Microsoftは、ラージオブジェクトヒープに関連する非効率性を回避するために、リストが84Kを超える場合に、複数の配列を使用する将来のバージョンを実装する可能性があります。バッキング配列が保護されたメンバーとして公開された場合、そのメンバーに依存するコードを壊さずにそのような変更を実装することは不可能です。
実際には、理想的なことは、リストアイテムインデックスが与えられると、指定されたアイテムを含む配列セグメントを返す保護されたメンバーを提供することによって、これらの利益のバランスを取ることであったかもしれません。配列が1つしかない場合、メソッドは常にその配列への参照を返します。オフセットはゼロ、開始添え字はゼロ、長さはリストの長さに等しくなります。 List<T>
の将来のバージョンが配列を複数の部分に分割する場合、メソッドは、派生クラスがそのようなアクセスなしでは不可能な方法で配列のセグメントに効率的にアクセスできるようにする可能性があります[例: Array.Copy
]を使用しますが、List<T>
は、適切に記述された派生クラスを壊すことなく、バッキングストアの管理方法を変更できます。不適切に記述された派生クラスは、基本実装が変更された場合に破損する可能性がありますが、それは基本ではなく派生クラスの障害です。
私は可能な限り非表示にするという原則に従っているため、デフォルトの場合は保護よりもプライベートを好みます。そのため、可視性をできるだけ低く設定します。
ここにたどり着きます。ただし、保護されたメンバー変数の使用は、継承することを計画しているだけでなく、派生クラス化されたプロパティが基本クラスで定義されたプロパティセッター/ゲッターを使用しないという確かな理由があるため、慎重に行う必要があると思います。
OOPでは、メンバーフィールドを「カプセル化」して、表現がアクセスおよび変更されるプロパティのプロパティを制御できるようにします。メンバー変数のベースでゲッター/セッターを定義するとき、これは基本的に、この変数を参照/使用する方法であると言っています。
基本クラスのgetter/setterメソッドで作成された動作を変更する必要がある設計主導の例外がありますが、これは代替案を慎重に検討した後に行われた決定であるように思われます。
たとえば、ゲッター/セッターを介さずに、派生クラスから直接メンバーフィールドにアクセスする必要がある場合、特定のプロパティを抽象として定義するか、派生クラスに移動する必要があると考え始めます。これは、階層の広さ、および追加の考慮事項の数によって異なります。しかし、私には、基本クラスで定義されたパブリックプロパティを歩き回るとにおいがし始めます。
もちろん、多くの場合、変数へのアクセス以外にゲッター/セッター内に何も実装していないため、「問題ではありません」。しかし、この場合も、派生クラスはゲッター/セッターを介して同じように簡単にアクセスできます。これはまた、一貫して採用されている場合、後で見つけるのが難しいバグから保護します。基本クラスのメンバーフィールドのゲッター/セッターの動作が何らかの方法で変更され、派生クラスが保護フィールドを直接参照している場合、問題が発生する可能性があります。
あなたは正しい方向に進んでいます。実装は、ユーザーまたは子孫によって変更されないことに依存しているため、何かをプライベートにします。
私はデフォルトでプライベートに設定し、それから私が公開する内部の仕組みの有無と量について意識的に決定します。あなたはそれがとにかく公開されることに基づいて作業しているようですので、それを続けてください。私たち二人がすべての目を交差させ、すべてのティーに点在することを覚えている限り、私たちは良いです。
それを見る別の方法はこれです。あなたがそれをプライベートにすると、誰かがあなたの実装で彼らが望むことをすることができないかもしれません。
プライベートにしないと、誰かがあなたの実装で本当にやりたくないことをすることができるかもしれません。
私は1993年のC++以来OOP、1995年のJava)をプログラミングしてきました。クラスを拡張または改訂する必要性を何度も見ています。クラスと緊密に統合された機能を追加します。OOPこれを行う方法は、基本クラスをサブクラス化し、サブクラスに変更を加えることです。たとえば、元々は他の場所でのみ参照されていた基本クラスフィールド基本クラスは他のアクションに必要です。または、他のアクティビティでフィールド(またはフィールドに含まれるメンバーの1つ)の値を変更する必要があります。そのフィールドが基本クラスでプライベートの場合、サブクラスはそれにアクセスできず、拡張できません。機能。フィールドが保護されている場合は、保護できます。
サブクラスは、クラス階層の他のクラスにはない基本クラスとの特別な関係があります。つまり、サブクラスは基本クラスのメンバーを継承します。継承の目的は、基本クラスのメンバーにアクセスすることです。プライベートは継承を阻止します。基本クラスの開発者は、サブクラスがメンバーにアクセスする必要がないことをどのように知っているのでしょうか。明確な場合もありますが、ルールではなくプライベートを例外にする必要があります。基本クラスをサブクラス化する開発者は基本クラスのソースコードを持っているので、代わりに基本クラスを直接修正することもできます(おそらく、サブクラス化する前にプライベートステータスを保護に変更するだけです)。それはクリーンで良い習慣ではありませんが、それがプライベートがあなたにやらせることです。
私はOOPの初心者ですが、ACMとIEEEの最初の記事から出回っています。覚えている限り、このスタイルの開発は何かをモデル化するためのものでした。現実の世界では、プロセスと操作には、「プライベート、保護、およびパブリック」要素が含まれます。したがって、オブジェクトに忠実であるために...。
何かをモデル化する以外に、プログラミングは問題を解決することです。 「プライベート、保護、およびパブリック」要素の問題は、信頼できるソリューションの作成に関連する場合にのみ問題になります。問題解決者として、私は他の人が自分の問題を解決するために私の解決策をどのように使用しているかに咳をするという間違いを犯しません。ここで、....の問題の主な理由は、データチェックの場所を許可することであったことを覚えておいてください(つまり、オブジェクトで使用する前に、データが有効な範囲と構造にあることを確認します)。
それを念頭に置いて、コードが設計された問題を解決する場合、あなたは仕事を終えています。他の人が同じまたは類似の問題を解決するためにあなたの解決策を必要とする場合-まあ、あなたは本当に彼らがそれを行う方法を制御する必要がありますか? 「利益を得ている場合、またはデザインの弱点を知っている場合にのみ、いくつかのものを保護する必要があります」と私は言います。