web-dev-qa-db-ja.com

なぜプライベートフィールドがあり、十分に保護されていないのですか?

クラスfields/properties/attributesの可視性privateは役に立ちますか? OOPでは、遅かれ早かれ、クラスのサブクラスを作成することになります。その場合、実装を理解し、実装を完全に変更できるようにするとよいでしょう。

クラスをサブクラス化するときに最初に行うことの1つは、privateメソッドの束をprotectedに変更することです。ただし、詳細を外界から隠すことが重要です。そのため、protectedだけでなくpublicも必要です。

私の質問は:privateの代わりにprotectedが良いツールである重要なユースケースについて知っていますか、または2つのオプション "protectedpublic "OOP言語には十分ですか?

267
Adam Libuša

あなたが言うように、protectedはまだ「実装を完全に変更する」能力をあなたに残しているからです。それはクラス内のものを真に保護するものではありません。

クラス内のものを「本物で保護する」ことに関心があるのはなぜですか。それ以外の場合クライアントコードを壊さずに実装の詳細を変更することは不可能です。言い換えると、サブクラスを作成する人々は、元の基本クラスを作成した人にとっても「外の世界」です。

実際には、protectedメンバーは基本的にクラスの「サブクラス用のパブリックAPI」であり、publicメンバーと同じように安定性と下位互換性を維持する必要があります。 true privateメンバーを作成する機能がない場合、実装のnothingは変更しても安全です。 (悪意のない)クライアントコードがなんとかしてそれに依存することができた可能性を排除することはできません。

ちなみに、「OOPでは遅かれ早かれ、クラスのサブクラスを作成する」というのは技術的には真実ですが、あなたの主張は「遅かれ早かれ、サブクラスを作成する」 everyclass "これはほぼ確実にそうではありません。

227
Ixrec

OOPでは、遅かれ早かれ、クラスのサブクラスを作成します

これは間違っています。すべてのクラスがサブクラス化されることを意図しているわけではなく、静的に型付けされた一部のOOP言語には、それを防ぐ機能さえあります。たとえば、final(JavaおよびC++)またはsealed (C#)。

理解し、実装を完全に変更できるようにするとよいでしょう。

いいえ、ちがいます。クラスがそのパブリックインターフェイスを明確に定義し、継承元であってもその不変条件を保持できるようにするのは良いことです。

一般に、アクセス制御は区分化に関するものです。コードの残りの部分とどのように相互作用するかを詳細に理解する必要なく、コードの個々の部分を理解する必要があります。プライベートアクセスはそれを可能にします。すべてが少なくとも保護されている場合は、基本クラスがどのように機能するかを理解するために、すべてのサブクラスが何をするかを理解する必要があります。

または、スコット・マイヤーズの言葉で言えば、クラスのプライベートな部分は、有限量のコード(クラス自体のコード)の影響を受けます。

パブリックパーツは、存在するすべてのコードと、まだ記述されていないすべてのコードによって影響を受ける可能性があります。

保護された部分は、既存のすべてのサブクラス、およびまだ作成されていないすべてのサブクラスの影響を受ける可能性があり、これも無限のコードです。

結論としては、保護されたものはパブリックよりもほとんど提供しませんが、プライベートは本当に改善されます。非公開ではなく、疑わしいのは、保護されたアクセス指定子の存在です。

255
Sebastian Redl

はい、プライベートフィールドは絶対に必要です。今週だけ、辞書に何を入れるかを制御するカスタム辞書実装を作成する必要がありました。辞書フィールドが保護された、または公開された場合、注意深く書いたコントロールは簡単に回避できたでしょう。

プライベートフィールドは通常、データが元のコーダーが期待したとおりであることを保護するためのものです。すべてを保護/公開し、これらの手順と検証を通じてコーチと馬に乗る。

33
Robbie Dee

正式に推論しようとするとき オブジェクト指向プログラムの正確性について典型的オブジェクト不変式 を含むモジュール方式を使用することです。このアプローチでは

  1. メソッドには、事前条件と事後条件(契約)が関連付けられています。
  2. オブジェクトは不変に関連付けられています。

オブジェクトに関するモジュラー推論は次のように進行します(少なくとも最初は近似です)。

  1. オブジェクトのコンストラクターが不変式を確立することを証明する
  2. 非プライベートメソッドごとに、オブジェクトの不変条件とメソッドの前提条件がエントリに保持されていると想定し、コードの本体がメソッドの終了条件と不変条件が保持されていることを示していることを証明します。

上記のアプローチを使用してobject Aを検証するとします。次に、method g of object Bを呼び出すmethod f of object Aを検証したいとします。モジュラー推論により、method gの実装を再検討することなく、method fについて推論することができます。 object Aの呼び出しサイトでmethod fの不変条件とmethod g,の前提条件を確立できれば、method fのポスト条件を、メソッド呼び出し。さらに、呼び出しが返された後でもAの不変式が保持されていることもわかります。

この推論のモジュール性により、大規模なプログラムについて正式に考えることができます。それぞれの方法について個別に推論し、この推論の結果をまとめて、プログラムのより大きな部分について推論することができます。

プライベートフィールドは、このプロセスで非常に役立ちます。オブジェクトの不変条件がそのオブジェクトの2つのメソッド呼び出し間で保持され続けることを知るために、通常、オブジェクトがその間に変更されないという事実に依存します。

オブジェクトにプライベートフィールドがないコンテキストで機能するモジュラー推論の場合、別のオブジェクトによってフィールドが設定された場合でも、不変式が常に再確立されることを保証する何らかの方法が必要になります(フィールドの後)セットする)。オブジェクトのフィールドがどのような値を持っているかに関係なくオブジェクトの不変式を想像することは困難であり、プログラムの正確さを推論するのにも役立ちます。フィールドへのアクセスに関する複雑な規則を考案する必要があるでしょう。そしておそらく、モジュール化された推論の能力の一部(最悪でもすべて)も失うことになります。

保護フィールド

保護フィールドは、モジュール的に推論する私たちの能力の一部を回復します。言語によっては、protectedにより、フィールドをすべてのサブクラス、またはすべてのサブクラスと同じパッケージクラスに設定する機能が制限される場合があります。作成しているオブジェクトの正確さについて推論しているときに、すべてのサブクラスにアクセスできない場合がよくあります。たとえば、後でより大きなプログラム(またはいくつかのより大きなプログラム)で使用されるコンポーネントまたはライブラリを作成している場合があります。それらの一部はまだ作成されていない場合もあります。通常、サブクラス化されているかどうか、またどのようにサブクラス化されているかはわかりません。

ただし、通常は、拡張するクラスのオブジェクトの不変を維持するために、サブクラスに義務があります。したがって、保護が「サブクラス」のみを意味し、サブクラスが常にスーパークラスの不変条件を維持するように規律付けられている言語では、プライベートではなく保護を使用するという選択は最小限のモジュール性しか失わないと主張することができます。 。

私は正式な推論について話してきましたが、プログラマがコードの正確さについて非公式な理由を説明するとき、彼らは時々同様のタイプの引数に依存することも よく考えられる です。

12
flamingpenguin

クラス内のprivate変数は、protectedブロック内のbreakステートメントがgoto labelよりも優れているのと同じ理由で、switchよりも優れていますステートメント; 人間のプログラマーはエラーが発生しやすいということです。

protected変数は、gotoステートメントがスパゲッティコードの作成に役立つのと同様に、意図しない悪用(プログラマーのミス)に役立ちます。

protectedクラス変数を使用して、バグのない実用的なコードを書くことはできますか?はい、もちろん! goto;を使用して、バグのない実用的なコードを書くことができるのと同じです。しかし、決まり文句が進むにつれて「できるからといって、そうすべきだという意味ではありません!」

クラス、実際にはOOパラダイム)は、エラーを起こしやすい人間のプログラマーがミスを犯すのを防ぐために存在します。人為的ミスに対する防御は、クラスに組み込まれた防御策と同じくらい優れています。クラスprotectedの実装は、要塞の壁に巨大な穴を開けるのと同じです。

基本クラスには、派生クラスに関する知識はまったくありません。基本クラスに関する限り、protectedは、実際にはpublicよりも多くの保護を提供しません。これは、派生クラスがpublicゲッター/セッターを作成するのを妨げるものがないためですこれはバックドアのように動作します。

基本クラスが内部実装の詳細への妨害されないアクセスを許可する場合、クラス自体がミスを防ぐことができなくなります。基本クラスには派生クラスに関する知識がまったくないため、これらの派生クラスで発生した間違いを防ぐ方法はありません。

基本クラスでできる最善のことは、その実装のできるだけ多くをprivateに隠し、派生クラスやクラス外の他のものからの変更を壊さないように十分な制限を設けることです。

最終的に、人為的エラーを最小限に抑えるための高級言語が存在します。人為的エラーを最小限に抑えるために、優れたプログラミング手法( SOLID原則 など)も存在します。

優れたプログラミング手法を無視するソフトウェア開発者は、失敗する可能性がはるかに高く、壊れた保守不可能なソリューションを生成する可能性が高くなります。優れた実践に従う人は、失敗する可能性がはるかに低く、実用的な保守可能なソリューションを作成する可能性が高くなります。

8
Ben Cottrell

継承可能なクラスには、オブジェクト参照のホルダーと派生クラスの2つのコントラクトがあります。パブリックメンバーは参照ホルダーとの契約によって拘束され、保護メンバーは派生クラスとの契約によって拘束されます。

メンバーをprotectedにすると、より汎用性の高い基本クラスになりますが、クラスの将来のバージョンが変更される可能性がある方法が制限されることがよくあります。メンバーをprivateにすることで、クラス作成者はクラスの内部動作を変更するためのより多様性を実現できますが、そこから派生できるクラスの種類は制限されます。

例として、.NETのList<T>はバッキングストアをプライベートにします。保護されている場合、派生型は他の方法では不可能な便利な機能を実行できますが、List<T>の将来のバージョンでは、数百万のアイテムを保持するリストでも、不格好なモノリシックバッキングストアを使用する必要があります。バッキングストアをプライベートにすると、List<T>の将来のバージョンで、派生クラスを壊すことなく、より効率的なバッキングストアを使用できるようになります。

4
supercat

誰かがクラスを書くとき、誰がそのクラスを将来拡張するのか、そしてどのような理由でそれを拡張するのかわからないというあなたの議論には重要な仮定があると思います。この仮定が与えられれば、あなたの議論は完全に理にかなっているでしょう。なぜなら、あなたがプライベートにしたすべての変数は、将来開発の道をいくらか遮断する可能性があるからです。しかし、私はその仮定を拒否します。

その仮定が拒否された場合、考慮すべきケースは2つだけです。

  1. 元のクラスの作成者は、それが拡張される可能性がある理由について非常に明確なアイデアを持っていました(たとえば、それはBaseFooであり、今後いくつかの具体的なFoo実装が存在します)。

この場合、作成者は、誰かがクラスを拡張することとその理由を知っているため、何を保護し、何を非公開にするかを正確に知っています。彼らはプライベート/保護された区別を使用して、サブクラスを作成しているユーザーに一種のインターフェースを伝えています。

  1. 子クラスの作成者は、いくつかの動作を親クラスにハッキングしようとしています。

このケースはまれであり(正当ではないと主張することもできます)、元のコードベースの元のクラスを変更するだけでは好ましくありません。また、デザインの不良の兆候である可能性もあります。これらの場合、私は友人(C/C++)とsetAccessible(true)(Java)のような他のハックを使用するだけでビヘイビアをハッキングする人を好みます。

私はその仮定を拒否しても安全だと思います。

これは一般に 継承よりも構成 の考え方にフォールバックします。継承は多くの場合、コードの再利用を減らす理想的な方法として教えられていますが、コードの再利用の最初の選択肢になることはめったにありません。私は単純なノックダウンの議論を持っていません、そしてそれは理解することはかなり困難で論争になるかもしれません。ただし、ドメインモデリングの経験から、誰がクラスを継承するのか、またその理由を明確に理解していないと、継承を使用することはめったにありません。

4
Pace

3つのアクセスレベルすべてに使用例があります。OOPは、それらのいずれかがないと不完全になります。通常、

  • すべての変数/データメンバーを作成しますprivate。あなたは、外部の誰かがあなたの内部データを台無しにしたくないのです。また、publicまたはprotectedインターフェースに補助機能(いくつかのメンバー変数に基づく計算を考える)を提供するメソッド-これは、内部で使用し、将来的に変更/改善したい場合があります。
  • クラスの一般的なインターフェイスを作成しますpublic。これが、元のクラスのユーザーが使用することになっているものであり、派生クラスも同様に見えるはずだと考えています。適切なカプセル化を提供するために、これらは通常、メソッドではなく(ユーザーがメソッドを操作するために必要なヘルパークラス/構造体、列挙型、typedefsのみ)、変数ではありません。
  • メソッドの宣言protectedこれは、クラスの機能を拡張/専門化したい人に役立つ可能性がありますが、publicの一部であってはなりませんインターフェース-実際には、通常、必要に応じてprivateメンバーをprotectedにレイズします。確信が持てない場合は、それがわかるまで
    1. クラスはサブクラス化できます/可能性があります/サブクラス化されます
    2. そして、サブクラス化のユースケースが何であるかもしれないかについて明確な考えを持っています。

正当な理由™がある場合のみ、この一般的なスキームから逸脱します。 「これにより、外部から自由にアクセスできるようになると、私の生活が簡単になります」(およびoutsideここにもサブクラスが含まれます)に注意してください。クラス階層を実装するとき、サブクラス化/拡張/特殊化してフレームワーク/ツールキットの基本クラスになり、元の機能の一部を1レベル上に移動するまで、保護されたメンバーのないクラスから始めることがよくあります。

2
Murphy

ここでは良い答えがたくさんありますが、とにかく2セントを投入します。 :-)

グローバルデータが悪いのと同じ理由でプライベートは良いです。

クラスがデータをプライベートと宣言する場合、このデータを乱すコードはクラス内のコードだけであることは確実です。バグがある場合、このデータを変更する可能性のあるすべての場所を見つけるために、作成全体を検索する必要はありません。あなたはそれがクラスにあることを知っています。コードに変更を加え、このフィールドの使用方法について何かを変更する場合、このフィールドを使用する可能性のあるすべての潜在的な場所を追跡し、計画された変更がそれらを壊すかどうかを調べる必要はありません。クラスの中にあるのは唯一の場所です。

ライブラリにあり、複数のアプリで使用されるクラスに変更を加えなければならないことが何度もありました。また、何も知らないアプリを壊さないように非常に注意深く踏まなければなりません。パブリックで保護されたデータが多いほど、問題が発生する可能性が高くなります。

1
Jay

私はいくつかの反対意見を述べる価値があると思います。

理論的には、他の回答で言及されているすべての理由でアクセスレベルを制御することは良いことです。

実際には、コードレビューの際に、(プライベートを使用したい)人がアクセスレベルをプライベート->保護から、保護->パブリックからあまり頻繁に変更しないことがよくあります。ほとんどの場合、クラスプロパティの変更には、セッター/ゲッターの変更が含まれます。これらは、私の時間(コードレビュー)とその時間(コードの変更)の多くを無駄にしました。

また、そのクラスが変更のために閉じられていないことを意味します。

これは、必要に応じていつでも変更できる内部コードに関するものです。コードの変更がそれほど簡単ではない場合、サードパーティのコードの方が状況は悪化します。

それでは、何人のプログラマーが面倒だと思いますか?さて、プライベートを持たないプログラミング言語をどれだけ使用していますか?もちろん、人々はプライベートな指定子を持たないためにそれらの言語を使用しているだけではありませんが、言語を単純化するのに役立ち、単純さが重要です。

Imo動的/静的型付けに非常に似ています。理論的には、静的型付けは非常に優れています。実際には、2%程度のエラーを防止するだけです ダイナミックタイピングの不合理な効果... 。プライベートを使用すると、それよりも少ないエラーを防ぐことができます。

SOLID原則は良いと思います。私は、人々がパブリック、保護、プライベートのクラスを作成することよりもそれらを気にかけることを望みます。

1
imel96

おそらくもっと興味深い質問は、なぜ私的以外のタイプのフィールドが必要なのかということです。サブクラスがスーパークラスのデータと対話する必要がある場合、これを行うと2つの間に直接結合が作成されますが、2つの間の対話を提供するメソッドを使用すると、レベルを間接的に変更できるため、そうでなければ非常に難しいスーパークラス。

多くの言語(RubyやSmalltalkなど)はパブリックフィールドを提供していないため、開発者はクラス実装への直接結合を許可しないでください。 (スーパークラスは常にサブクラスの保護されたアクセサーを提供できるため)一般性は失われませんが、クラスが常に少なくともサブクラスから少しずつ分離されることが保証されます。これが一般的な設計ではないのはなぜですか?

1
Jules

プライベートメソッド/変数は、通常、サブクラスから隠されます。それは良いことです。

プライベートメソッドは、パラメーターに関する仮定を作成し、呼び出し元に健全性チェックを任せることができます。

保護されたメソッドすべき健全性チェック入力。

0
Phil Lello

protectedでは不十分な理由のもう1つの実際的な例も追加したいと思います。私の大学では、最初の数年間、ボードゲームのデスクトップバージョンを開発する必要があるプロジェクトに着手しました(後でAIが開発され、ネットワークを介して他のプレイヤーに接続されます)。テストフレームワークを含めて、それらを開始するためにいくつかの部分的なコードが提供されます。メインゲームクラスの一部のプロパティはprotectedとして公開されているため、このクラスを拡張するテストクラスはそれらにアクセスできます。ただし、これらのフィールドは機密情報ではありません。

単元のTAとして、生徒は追加されたすべてのコードを単にprotectedまたはpublicにしているのをよく見ます(おそらく、他のprotectedおよびpublicのものを見たためです)そして彼らはそれに倣うべきであると仮定した)なぜ彼らの保護レベルが不適切であるのかを彼らに尋ねます、そして多くの人はその理由を知りません。答えは、彼らがサブクラスに公開している機密情報は、別のプレイヤーがそのクラスを拡張し、ゲームの非常に機密情報にアクセスすることで、だまされる可能性があることを意味します(基本的に、対戦相手は場所を隠します。一部のクラスを拡張することで、戦艦のボードで対戦相手の駒を見ることができます)。そのため、ゲームのコンテキストではコードが非常に危険になります。

それ以外にも、サブクラスでさえもプライベートにしておく理由は他にもたくさんあります。実行内容を必ずしも知らない人が変更した場合、クラスの正しい動作を台無しにする可能性のある実装の詳細を非表示にすることがあります(主に、ここでコードを使用する他の人について考えます)。

0
J_mie6