オブジェクト指向プログラミングでクラス/構造のプライベート/保護されたメンバーを持つ目的は何ですか?すべてのメンバーを公開することの害は何ですか?
10 に答える
カプセル化。つまり、クラスデータの実装を非表示にします。これにより、すべてのクライアントコードを壊すことなく、後で変更することができます。たとえば、
class MyClass {
public int foo;
}
あなたのクライアントは次のようなコードを書くかもしれません
MyClass bar = new MyClass();
bar.foo++;
これfoo
で、実際にはintではなくdoubleである必要があることに気付いた場合は、次のように変更します。
class MyClass {
public double foo;
}
そしてクライアントコードはコンパイルに失敗します:-(
適切に設計されたインターフェイスを使用すると、内部(プライベートパーツ)の変更に、メンバー変数を計算に変換したり、その逆を行ったりすることもできます。
class Person {
public String getName();
public String getStreetAddress();
public String getZipCode();
public String getCountryCode();
public int hashCode();
}
(簡単にするためにStringプロパティを使用します。実際の設計では、これらのいくつかはおそらく独自のタイプを持つに値します。)
この設計では、たとえば、Address
住所、郵便番号、国コードを含むプロパティを内部に導入し、クライアントが何も気付かないうちに、代わりにこのプライベートメンバーのフィールドを使用するようにアクセサーを書き直すことができます。
毎回ハッシュコードを計算するか、パフォーマンスを向上させるためにプライベート変数にキャッシュするかを自由に決めることもできます。ただし、そのキャッシュフィールドが公開されている場合は、誰でも変更できるため、ハッシュマップの動作が台無しになり、微妙なバグが発生する可能性があります。したがって、カプセル化は、オブジェクトの内部状態の一貫性を保証する上で重要です。たとえば、上記の例では、セッターは郵便番号と国コードを簡単に検証して、無効な値を設定するのを防ぐことができます。郵便番号の形式が実際の国で有効であることを確認することもできます。つまり、複数のプロパティにまたがる有効基準を確認することもできます。適切に設計されたインターフェイスを使用すると、たとえば、両方のプロパティを同時に設定するためのセッターのみを提供することで、このバインディングを適用できます。
public void setCountryCodeAndZip(String countryCode, String zipCode);
ただし、パブリックフィールドでは、これらの選択肢はありません。
プライベートフィールドの特別なユースケースは不変オブジェクトです。String
これは、Javaなどで非常に一般的です。例はとBigDecimal
です。これらのクラスにはパブリックセッターがまったくありません。これにより、オブジェクトが作成されると、状態が変更されないことが保証されます。これにより、多くのパフォーマンスの最適化が可能になるだけでなく、マルチスレッドプログラム、ORMなどでの使用が容易になります。
ウィキペディアの情報隠蔽のトピックを読むことをお勧めします。
基本的に、プライベートメンバーは、クラスがその実装の詳細を外部のコンシューマーから隠すことを許可します。これにより、クラスはデータと動作の表現方法をより適切に制御でき、消費者はクラスの主な目的に関係のない詳細を知らないようになります。
実装の詳細を非表示にすると、クラスの外部のコードがそれらの詳細への依存関係を確立できないようにすることで、プログラムの保守性が向上します。これにより、実装を外部のコンシューマーから独立して変更でき、既存の動作を壊すリスクが軽減されます。プライベート実装の詳細が公開された場合、それらの詳細に依存するクラスのコンシューマーを壊す可能性なしに変更することはできません。
プライベートメンバーは、クラスが外部からの悪用から実装を保護することも許可します。通常、クラスの状態には、状態が有効な場合と無効な場合を定義する内部依存関係があります。状態情報の有効性を管理するルールは不変であると見なすことができます。つまり、クラスは常にそれらが真であると期待します。プライベートな詳細を公開すると、外部コードが不変条件に違反する可能性のある方法でこの状態を変更できるようになり、クラスの有効性(および動作)が損なわれる可能性があります。
情報隠蔽の追加の利点は、クラスの消費者がクラスと適切に対話するために理解しなければならない表面積を減らすことです。単純化は一般的に良いことです。これにより、消費者は、クラスがその機能をどのように実現するかではなく、パブリックインターフェイスの理解に集中できます。
セクション7.4:このオンラインC++チュートリアルのプライベートパーツを保護するでうまく説明されています。
なぜこのようなものを気にするのですか?
指定子を使用すると、クラスを非常に複雑にし、多くのメンバー関数とデータメンバーを使用しながら、他のクラスで使用できる単純なパブリックインターフェイスを使用できます。200個のデータメンバーと100個のメンバー関数を持つクラスは、作成が非常に複雑になる可能性があります。ただし、パブリックメンバーの機能が3つまたは4つしかなく、残りがすべてプライベートである場合、誰かがクラスの使用方法を簡単に学ぶことができます。彼は、ほんの一握りのパブリック関数の使用方法を理解するだけでよく、このデータへのアクセスが許可されていないため、200のデータメンバーを気にする必要はありません。彼は、クラスのパブリックインターフェイスを介してのみプライベートデータにアクセスできます。間違いなく、小さなプログラムでは、これらの指定子を使用する必要はないように思われるかもしれません。でも、妥当なサイズ(数百行以上)のプログラムを実行する場合は、理解する価値があります。一般に、データメンバーを非公開にすることをお勧めします。クラスの外部から呼び出す必要のあるメンバー関数はパブリックである必要があり、クラス内からのみ呼び出されるメンバー関数(「ヘルパー関数」とも呼ばれます)はおそらくプライベートである必要があります。これらの指定子は、複数のプログラマーが関与する大規模なプログラムで特に役立ちます。
上記の説明は、使用private
することで学習曲線がどのように緩和されるかを説明しています。「コードブレイク」の側面を説明する例を次に示します。
ParameterIO
これが整数パラメータのベクトルを読み書きするクラスです
class ParameterIO
{
public:
// Main member
vector<int> *Params;
string param_path;
// Generate path
void GeneratePath()
{
char szPath[MAX_PATH];
sprintf(szPath,"params_%d.dat",Params->size());
param_path = szPath;
}
// Write to file
void WriteParams()
{
assert_this(!Params->empty(),"Parameter vector is empty!");
ofstream fout(param_path.c_str());
assert_this(!fout.fail(),"Unable to open file for writing ...");
copy(Params->begin(),Params->end(),ostream_iterator<int>(fout,"\n"));
fout.close();
}
// Read parameters
void ReadParams(const size_t Param_Size)
{
// Get the path
Params->resize(Param_Size);
GeneratePath();
// Read
ifstream fin(param_path.c_str());
assert_this(!fin.fail(),"Unable to open file for reading ...");
// Temporary integer
for(size_t i = 0; i < Params->size() && !fin.eof() ; ++i) fin>>(*Params)[i];
fin.close();
}
// Constructor
ParameterIO(vector<int> * params):Params(params)
{
GeneratePath();
}
// Destructor
~ParameterIO()
{
}
// Assert
void assert_this(const bool assertion, string msg)
{
if(assertion == false)
{
cout<<msg<<endl;
exit(1);
}
}
};
次のコードはこのクラスを壊します:
const size_t len = 20;
vector<int> dummy(len);
for(size_t i = 0; i < len; ++i) dummy[i] = static_cast<int>(i);
ParameterIO writer(&dummy);
// ParameterIO breaks here!
// param_path should be private because
// the design of ParameterIO requires a standardized path
writer.param_path = "my_cool_path.dat";
// Write parameters to custom path
writer.WriteParams();
vector<int> dunce;
ParameterIO reader(&dunce);
// There is no such file!
reader.ReadParams(len);
比喩的に言えば、プライベート メンバーをパブリックとして公開することは、車のダッシュボードにエンジン オイルの圧力を微調整できるオプションがあるようなものです。
車はそれを内部で (非公開で) 管理する必要があり、ユーザーが直接いじることから保護する必要があります (カプセル化)。これには明らかな理由があります。
それは本当にあなたのイデオロギーに依存します。なんらかの理由で公開してはならない情報を非表示にするという考えです。
オンラインで公開したいライブラリがある場合、多くの人がそれをダウンロードし、コードで使用する人もいます。パブリック API を最小限に抑え、実装の詳細を非表示にすると、バグに遭遇したときやコードを改善したいときに API を更新するのに苦労することが少なくなります。
また、たとえば Java では、可視性を変更せずにメンバー変数へのアクセスを制限する方法がないため、時期尚早に getter と setter を作成し、変数自体を非公開または保護することに気付くことがよくあります。たとえば Python では、getter と setter を変数のように動作させて直接アクセスできるため (そこではプロパティと呼ばれます)、この問題は存在しません。
最後に、一貫した状態を有効にする必要があり、直接アクセスすると問題が発生するメソッドが必要になる場合があります。
経験則では、何かを公開すると、誰かがそれを使用します。そしてほとんどの場合、彼らは間違った理由でそれを使用します (つまり、意図した使用方法とは異なります)。この場合、情報隠蔽は武器キャビネットのチャイルドロックに相当します。
ピーターの答えに追加するには、クラスに名前が保存されていて、それを単一の名前文字列から名文字列と姓文字列に変更したいとします。メンバーが public の場合、他のクラスが name 変数を直接読み取る (または書き込む) 可能性があり、その変数が消えると壊れる可能性があります。
言うまでもなく、他のクラスにメンバーを編集する機能をまったく持たせたくない場合もあります。
人体に内臓がある目的は何ですか?すべての臓器を屋外に置くことの害は何ですか?
丁度!
簡単な答えは次のとおりです。それらが必要なので、それらなしでは生きられず、変更して遊んでもらうためにそれらをすべての人に公開することはできません。
誰にも個人情報を明かしたくない場合があります。たとえば、年齢を公開したくないが、25 歳以上であることは伝えたい場合があります。
客層やクラスの消費にもよるが、全く害はない。浸透するように、もう 1 度繰り返します。
客層やクラスの消費にもよるが、全く害はない。
1 人か 2 人で 1 か月ほどかかる小さなプロジェクトの多くでは、すべてのプライベート定義とパブリック定義を完全に下ろすと、作業負荷が大幅に増加する可能性があります。ただし、複数のチームが存在する可能性があり、チームが地理的に一緒に配置されていない大規模なプロジェクトでは、最初にすべてのパブリック インターフェイスを適切に設計することで、プロジェクト全体の成功の可能性を大幅に高めることができます。
したがって、この質問に答える前に、クラスがどのように消費され、誰によって消費されるのかを実際に確認する必要があります。同様に、ソフトウェア開発のライフサイクルはどのくらいになりますか? 何ヶ月ですか?年?何十年?あなた以外にクラスを利用する人はいますか?
クラスがより「公開」されればされるほど (つまり、クラスを消費して使用する人が増えるほど)、堅固な公開インターフェースを確立し、それに固執することがより重要になります。
簡単な例: その値に対して特定の条件を確保する必要がある場合があります。この場合、直接設定すると、このような状態が壊れる可能性があります。
「みんなに読んでもらいたくないかもしれない」という議論をする人が多いですが、値を設定するという制約はより使いやすい例だと思います。