私はめったに継承を使用しませんが、継承されたクラスのカプセル化を壊すと思うので、使用する場合は保護された属性を使用しません。
保護された属性を使用していますか? 何に使うの?
私はめったに継承を使用しませんが、継承されたクラスのカプセル化を壊すと思うので、使用する場合は保護された属性を使用しません。
保護された属性を使用していますか? 何に使うの?
Bill Venners による Design に関するこのインタビューで、 Effective Javaの著者である Joshua Bloch は次のように述べています。
サブクラスの信頼
Bill Venners: 非サブクラスよりもサブクラスをより親密に信頼すべきですか? たとえば、非サブクラスの実装よりもサブクラスの実装の方が壊れやすいですか? 特に、保護されたデータについてどう思いますか?
ジョシュ・ブロック:サブクラス化可能であり、悪意のあるサブクラスに対して堅牢であるものを作成することは、サブクラスに内部データ構造へのアクセスを許可すると仮定すると、実際にはかなり難しいことです。サブクラスが通常のユーザーがアクセスできないものにアクセスできない場合、サブクラスがダメージを与えるのは難しくなります。しかし、すべてのメソッドを final にしない限り、サブクラスはメソッドの呼び出しに応じて間違ったことをするだけで契約を破ることができます。これがまさに、String のようなセキュリティ クリティカルなクラスが final である理由です。そうしないと、誰かが文字列を変更可能に見せるサブクラスを作成する可能性があり、セキュリティを破るのに十分です。したがって、サブクラスを信頼する必要があります。彼らを信用しないなら、許すことはできません。
一般的に保護されたデータに関する限り、それは必要悪です。最小限に抑える必要があります。ほとんどの保護されたデータと保護されたメソッドは、実装の詳細にコミットすることになります。保護されたフィールドは、サブクラスから見えるようにする実装の詳細です。保護されたメソッドでさえ、サブクラスから見えるようにする内部構造の一部です。
見えるようにする理由は、サブクラスが仕事をできるようにするため、または効率的に行うためにしばしば必要になるからです。しかし、一度それをやると、あなたはそれにコミットします。特定のフィールドやメソッドの使用を必要としない、より効率的な実装を後で見つけたとしても、現在は変更が許可されていません。
したがって、他のすべての条件が等しい場合、保護されたメンバーはまったく存在しないはずです。ただし、数が少なすぎると、そのクラスはスーパー クラスとして使用できないか、少なくとも効率的なスーパー クラスとして使用できない可能性があります。後から分かることが多いです。私の哲学は、最初にクラスを作成するときに、保護されたメンバーをできるだけ少なくすることです。次に、それをサブクラス化してみてください。特定の保護されたメソッドがないと、すべてのサブクラスが何か悪いことをしなければならないことに気付くかもしれません。
例として、 を見ると
AbstractList
、リストの範囲を一発で削除する保護されたメソッドがあることがわかります (removeRange
)。なぜそれがそこにあるのですか?パブリック API に基づいて範囲を削除する通常のイディオムは、 を呼び出しsubList
て sub- を取得しList
、次にclear
その sub-を呼び出すことList
です。ただし、この特定の保護されたメソッドがなければ、clear
個々の要素を繰り返し削除することしかできません。考えてみてください。配列表現がある場合、それは何をしますか? 配列を繰り返し折りたたんで、順序 N の作業を N 回行います。したがって、線形の作業量ではなく、二次的な作業量が必要になります。この保護されたメソッドを提供することにより、範囲全体を効率的に削除できる実装を許可します。また、妥当な
List
実装であれば、範囲をより効率的に一度に削除できます。この保護されたメソッドが必要になることは、あなたが私よりもずっと賢くなければならないことです。基本的に、私はそれを実装しました。その後、サブクラス化を開始すると、範囲削除が 2 次式であることに気付きました。余裕がなかったので、保護されたメソッドを入れました。それが保護されたメソッドを使用した最善のアプローチだと思います。できるだけ少なくしてから、必要に応じてさらに追加します。保護されたメソッドは、変更する可能性のある設計へのコミットメントを表します。保護されたメソッドはいつでも追加できますが、削除することはできません。
Bill Venners: そして、保護されたデータは?
Josh Bloch:同じことですが、それ以上です。保護されたデータは、データの不変条件を台無しにするという点でさらに危険です。他の誰かに内部データへのアクセスを許可した場合、彼らはそれを自由に管理できます。
短いバージョン: カプセル化を破りますが、最小限に抑える必要がある悪です。
C#:
基本クラスでオーバーライドする抽象メソッドまたは仮想メソッドに protected を使用します。また、基本クラスによって呼び出される可能性がある場合はメソッドを保護しますが、クラス階層の外で呼び出されることは望ましくありません。
同じパッケージのサブクラスまたはクラス (Java に関するものである場合) の恩恵を受ける静的 (または「グローバル」) 属性にそれらが必要になる場合があります。
ある種の「定数値」を表すこれらの静的最終属性にはゲッター関数がほとんどないため、その場合、保護された静的最終属性が意味を持つ場合があります。
Scott Meyers は、Effective C++ (第 3 版) では保護された属性を使用しないと述べています。
項目 22: データ メンバーを非公開に宣言します。
理由はあなたが与えるのと同じです:それはカプセル化を破ります。その結果、クラスのレイアウトをローカルに変更すると、依存型が壊れて、他の多くの場所が変更される可能性があります。
私は最近、「保護された」メンバーが非常に良いアイデアであるというプロジェクトに取り組みました。クラス階層は次のようなものでした。
[+] Base
|
+--[+] BaseMap
| |
| +--[+] Map
| |
| +--[+] HashMap
|
+--[+] // something else ?
Base は std::list を実装しましたが、他には何も実装しませんでした。ユーザーがリストに直接アクセスすることは禁止されていましたが、基本クラスが不完全だったため、派生クラスに依存してリストへの間接化を実装していました。
間接化は、少なくとも 2 つのフレーバー (std::map と stdext::hash_map) から発生する可能性があります。両方のマップは同じように動作しますが、hash_map にはキーがハッシュ可能である必要があります (VC2003 では、size_t にキャスト可能)。
そのため、BaseMap はマップのようなコンテナーであるテンプレート型として TMap を実装しました。
Map と HashMap は BaseMap の 2 つの派生クラスで、1 つは std::map で BaseMap を特殊化し、もう 1 つは stdext::hash_map で特殊化しています。
そう:
Base はそのままでは使用できず (パブリック アクセサーはありません!)、共通の機能とコードのみを提供していました。
BaseMap には、std::list への簡単な読み取り/書き込みが必要でした
Map と HashMap には、BaseMap で定義された TMap への簡単な読み取り/書き込みアクセスが必要でした。
私にとって唯一の解決策は、std::list と TMap メンバー変数に protected を使用することでした。とにかく、読み取り/書き込みアクセサーを介してすべてまたはほとんどすべての機能を公開するため、それらを「プライベート」にする方法はありませんでした。
最後に、クラスを複数のオブジェクトに分割し、それぞれの派生がその母クラスに必要な機能を追加し、最も派生したクラスのみが実際に使用できるようになる場合は、保護する方法が適していると思います。「保護されたメンバー」がクラスであり、「壊す」ことはほとんど不可能だったという事実が助けになりました。
ただし、それ以外の場合は、protected をできるだけ避ける必要があります (つまり、デフォルトで private を使用し、メソッドを公開する必要がある場合は public を使用します)。
Java では保護された属性を使用しません。Java では保護されたパッケージのみであるためです。しかし C++ では、継承クラスがそれらを直接継承できるように、抽象クラスでそれらを使用します。
属性を保護する正当な理由はありません。基本クラスは、状態に依存できる必要があります。これは、アクセサー メソッドを介したデータへのアクセスを制限することを意味します。子供であっても、個人データへのアクセスを許可することはできません。
それはあなたが望むものに依存します。高速なクラスが必要な場合は、データを保護し、保護されたパブリック メソッドを使用する必要があります。あなたのクラスから派生したユーザーはあなたのクラスをよく知っているか、少なくともオーバーライドしようとしている関数のマニュアルを読んでいると想定する必要があると思うからです。
ユーザーがクラスを台無しにしても、それはあなたの問題ではありません。悪意のあるすべてのユーザーは、仮想の 1 つをオーバーライドするときに次の行を追加できます。
(C#)
static Random rnd=new Random();
//...
if (rnd.Next()%1000==0) throw new Exception("My base class sucks! HAHAHAHA! xD");
//...
これを防ぐためにすべてのクラスを封印することはできません。
もちろん、いくつかのフィールドに制約が必要な場合は、アクセサー関数またはプロパティ、または必要なものを使用して、そのフィールドをプライベートにします。他に解決策がないためです...
しかし、個人的には、何があっても oop の原則に固執するのは好きではありません。特に、データ メンバーを非公開にすることのみを目的としてプロパティを作成します。
(C#):
private _foo;
public foo
{
get {return _foo;}
set {_foo=value;}
}
これは私の個人的な意見でした。
ただし、上司が要求することを行います (上司がそれよりもプライベート フィールドを必要とする場合)。
一般に、保護されたデータ メンバーを使用する必要はありません。これは、API を作成する場合に二重に当てはまります。誰かがあなたのクラスから継承すると、実際にメンテナンスを行うことはできず、奇妙で時にはワイルドな方法でそれらを壊すことはできません。
メソッドに変更する予定がないことがわかっている基本クラス内で保護された変数/属性を使用しています。そうすれば、サブクラスは継承された変数に完全にアクセスでき、それらにアクセスするために getter/setter を経由する (人為的に作成された) オーバーヘッドがありません。例として、基礎となる I/O ストリームを使用するクラスがあります。サブクラスが基礎となるストリームに直接アクセスできないようにする理由はほとんどありません。
これは、基本クラスとすべてのサブクラス内で直接単純な方法で使用されるメンバー変数には問題ありません。しかし、より複雑な用途を持つ変数 (たとえば、それにアクセスすると、クラス内の他のメンバーに副作用が生じる) の場合、直接アクセス可能な変数は適切ではありません。この場合、プライベートにすることができ、代わりにパブリック/保護されたゲッター/セッターを提供できます。例として、基本クラスによって提供される内部バッファリング メカニズムがあります。この場合、サブクラスから直接バッファにアクセスすると、基本クラスがそれらを管理するために使用するアルゴリズムの整合性が損なわれます。
これは、メンバー変数がどれほど単純であるか、および将来のバージョンでどのように期待されるかに基づいた、設計上の判断による決定です。
カプセル化は優れていますが、行き過ぎてしまう可能性があります。独自のプライベート メソッドが getter/setter メソッドのみを使用してメンバー変数にアクセスするクラスを見てきました。クラスが独自のプライベート データを使用して独自のプライベート メソッドを信頼できない場合、誰を信頼できるのでしょうか。
私はそれらを使用します。要するに、いくつかの属性を共有したい場合、これは良い方法です。確かに、それらに対して set/get 関数を作成することはできますが、検証がない場合、何の意味があるのでしょうか? また、高速です。
これを考慮してください。基本クラスであるクラスがあります。子オブジェクトで使用したくない属性がかなりあります。それぞれに対して get/set 関数を作成することも、単に設定することもできます。
私の典型的な例は、ファイル/ストリーム ハンドラです。ハンドラー (つまり、ファイル記述子) にアクセスしたいが、他のクラスから隠したい。そのための set/get 関数を書くよりもずっと簡単です。
保護された属性は悪い考えだと思います。私はCheckStyleを使用して、Java 開発チームにそのルールを適用しています。
一般的に、はい。通常、保護されたメソッドの方が優れています。
使用時には、クラスのすべての子によって共有されるオブジェクトに保護された最終変数を使用することによって、ある程度の単純さが得られます。コントラクトをプリミティブやコレクションで定義することは不可能であるため、プリミティブやコレクションで使用することは常にお勧めします。
最近、プリミティブや生のコレクションで行うことと、整形式のクラスで行うことを分けるようになりました。プリミティブとコレクションは常に非公開にする必要があります。
また、パブリック メンバー変数が final 宣言されていて、あまり柔軟ではない整形式のクラス (プリミティブやコレクションではない) である場合に、パブリック メンバー変数を公開することもあります。
これはばかげたショートカットではありません。かなり真剣に考えた結果、オブジェクトを公開する public final変数と getterの間にはまったく違いがないと判断しました。