ゲッターとセッターは、適切なオブジェクト指向ではないと批判されることがよくあります。一方、私が見たほとんどのOOコードには、広範なゲッターとセッターがあります。
ゲッターとセッターはいつ正当化されますか?それらを使用しないようにしますか?彼らは一般的に使いすぎですか?
お気に入りの言語にプロパティがある場合(私の場合)、そのようなものもこの質問のゲッターおよびセッターと見なされます。 OO方法論の観点から見ると同じです。構文はより優れています。
ゲッター/セッター批評のソース(一部はコメントから引用してより良い可視性を提供します):
批判を簡単に述べると、ゲッターとセッターを使用すると、オブジェクトの外部からオブジェクトの内部状態を操作できます。これはカプセル化に違反しています。オブジェクト自体だけがその内部状態を気にする必要があります。
そして、コードの手続き型バージョンの例。
struct Fridge
{
int cheese;
}
void go_shopping(Fridge fridge)
{
fridge.cheese += 5;
}
コードのミューテーターバージョン:
class Fridge
{
int cheese;
void set_cheese(int _cheese) { cheese = _cheese; }
int get_cheese() { return cheese; }
}
void go_shopping(Fridge fridge)
{
fridge.set_cheese(fridge.get_cheese() + 5);
}
ゲッターとセッターにより、適切なカプセル化を行わずにコードがはるかに複雑になりました。内部状態は他のオブジェクトからアクセスできるため、これらのゲッターとセッターを追加してもそれほど多くは得られません。
質問は以前にスタックオーバーフローで説明されています。
ゲッターとセッターがあること自体はカプセル化を壊しません。カプセル化を解除するのは、何も考えずに、すべてのデータメンバー(すべてのfield、Java lingo))にゲッターとセッターを自動的に追加することです。すべてのデータメンバーをパブリックにするよりも、ほんの少しの距離です。
カプセル化のポイントは、オブジェクトの外側からオブジェクトの状態を認識したり変更したりできないようにすることではなく、それを行うための適切なpolicyを用意することです。
一部のデータメンバーは完全にオブジェクトの内部にあり、ゲッターもセッターも持たない場合があります。
一部のデータメンバーは読み取り専用にする必要があるため、getterは必要ですが、setterは必要ありません。
一部のデータメンバーは、互いに整合性を保つ必要がある場合があります。このような場合は、それぞれにセッターを提供するのではなく、それらを同時に設定する単一の方法を提供して、値の整合性をチェックできるようにします。
一部のデータメンバーは、一定量ずつ増減するなど、特定の方法でのみ変更する必要がある場合があります。この場合、セッターではなく、increment()
またはdecrement()
メソッドを提供します。
さらに、実際には読み書き可能である必要があり、ゲッターとセッターの両方を持つものもあります。
class Person
の例を考えてみましょう。人が名前、社会保障番号、年齢を持っているとしましょう。人々が自分の名前や社会保障番号を変更することは許可されていないとしましょう。ただし、その人の年齢は毎年1ずつ増加する必要があります。この場合、名前とSSNを指定された値に初期化し、年齢を0に初期化するコンストラクターを提供します。また、年齢を増加させるメソッドincrementAge()
を提供します。 1. 3つすべてのゲッターも提供します。この場合、セッターは必要ありません。
この設計では、クラスの外部からオブジェクトの状態を検査できるようにし、クラスの外部からオブジェクトの状態を変更できるようにします。ただし、状態を任意に変更することはできません。名前とSSNはまったく変更できないこと、および年齢は一度に1年ずつ増分できることを効果的に示すポリシーがあります。
今、人が給料も持っているとしましょう。そして、人々は自由に転職できます。つまり、給与も変わります。この状況をモデル化するには、setSalary()
メソッドを提供する以外に方法はありません。この場合、給与を自由に変更できるようにすることは完全に合理的な方針です。
ちなみに、あなたの例では、putCheese()
とtakeCheese()
の代わりに、クラスFridge
にget_cheese()
とset_cheese()
のメソッドを指定します。その後、まだカプセル化されています。
public class Fridge {
private List objects;
private Date warranty;
/** How the warranty is stored internally is a detail. */
public Fridge( Date warranty ) {
// The Fridge can set its internal warranty, but it is not re-exposed.
setWarranty( warranty );
}
/** Doesn't expose how the fridge knows it is empty. */
public boolean isEmpty() {
return getObjects().isEmpty();
}
/** When the fridge has no more room... */
public boolean isFull() {
}
/** Answers whether the given object will fit. */
public boolean canStore( Object o ) {
boolean result = false;
// Clients may not ask how much room remains in the fridge.
if( o instanceof PhysicalObject ) {
PhysicalObject po = (PhysicalObject)o;
// How the fridge determines its remaining usable volume is a detail.
// How a physical object determines whether it fits within a specified
// volume is also a detail.
result = po.isEnclosedBy( getUsableVolume() );
}
return result;
}
/** Doesn't expose how the fridge knows its warranty has expired. */
public boolean isPastWarranty() {
return getWarranty().before( new Date() );
}
/** Doesn't expose how objects are stored in the fridge. */
public synchronized void store( Object o ) {
validateExpiration( o );
// Can the object fit?
if( canStore( o ) ) {
getObjects().add( o );
}
else {
throw FridgeFullException( o );
}
}
/** Doesn't expose how objects are removed from the fridge. */
public synchronized void remove( Object o ) {
if( !getObjects().contains( o ) ) {
throw new ObjectNotFoundException( o );
}
getObjects().remove( o );
validateExpiration( o );
}
/** Lazily initialized list, an implementation detail. */
private synchronized List getObjects() {
if( this.list == null ) { this.list = new List(); }
return this.list;
}
/** How object expiration is determined is also a detail. */
private void validateExpiration( Object o ) {
// Objects can answer whether they have gone past a given
// expiration date. How each object "knows" it has expired
// is a detail. The Fridge might use a scanner and
// items might have embedded RFID chips. It's a detail hidden
// by proper encapsulation.
if( o implements Expires && ((Expires)o).expiresBefore( today ) ) {
throw new ExpiredObjectException( o );
}
}
/** This creates a copy of the warranty for immutability purposes. */
private void setWarranty( Date warranty ) {
assert warranty != null;
this.warranty = new Date( warranty.getTime() )
}
}
Javaでのゲッターとセッターの基本的な理由は非常に簡単です:
したがって、フィールドがインターフェースを通過できるようにする場合は、リーダーとライターのメソッドが必要になります。これらは、従来、フィールドxのgetXおよびsetXと呼ばれていました。
から http://www.adam-bien.com/roller/abien/entry/encapsulation_violation_with_getters_and から
JavaBeanスタイル:
connection.setUser("dukie");
connection.setPwd("duke");
connection.initialize();
OOスタイル:
connection.connect("dukie","duke");
まあ、明らかに私は後者のアプローチを好みます。実装の詳細をあっさりとばせず、よりシンプルで簡潔になり、必要なすべての情報がメソッド呼び出しに含まれているので、正しく理解するのが簡単です。可能な限り、コンストラクターのパラメーターを使用してプライベートメンバーを設定することも好みます。
あなたの質問は、ゲッター/セッターが正当化されるのはいつですか?おそらく、モードの変更が必要な場合、または情報を得るためにオブジェクトに問い合わせる必要がある場合です。
myObject.GetStatus();
myObject.SomeCapabilitySwitch = true;
考えてみると、最初にC#でコーディングを始めたとき、上に示したJavabeansスタイルで多くのコードを書きました。しかし、言語の経験を積むにつれて、コンストラクターでのメンバーの設定を増やし、上記のように見えるメソッドを使用するようになりましたOOスタイル。
ゲッターとセッターはいつ正当化されますか?
「取得」と「設定」の動作が実際にモデルの動作と一致する場合、これは実際にはまったくです。
ビジネスドメインの動作が明確でないため、他のすべての使用は単なるごまかしです。
編集
その答えはばかげたように出くわしたかもしれないので、拡大させてください。上記の答えはほぼ正しいですが、OO設計のプログラミングパラダイムと、全体像に欠けているものに焦点を当てています。私の経験では、これにより、取得とセッターを回避することは、 OOプログラミング言語(例:ゲッターをプロパティに置き換えるのはすばらしいと思います)
実際、これは設計プロセスの結果です。インターフェースやカプセル化、パターンに取り掛かる必要はありません。これらのパラダイムを壊すのか、壊さないのか、何が良いのか、何が悪いのかを議論するOOプログラミング。唯一のポイントは、最終的に重要なのは、ドメイン内に配置することによってこのように機能するものがドメインにない場合、ドメインをモデル化していないことです。
実際には、ドメイン空間のどのシステムにもゲッターとセッターがある可能性はほとんどありません。給与を担当している男性または女性のところまで歩いて、単に"Set this salary to X"または"Get me this salary"と言うことはできません。そのような行動は単に存在しない
これをコードに入れている場合は、ドメインのモデルに一致するようにシステムを設計していません。はい、それはインターフェースとカプセル化を壊しますが、それがポイントではありません。ポイントは、存在しないものをモデリングしているということです。
さらに、あなたがおそらく重要なステップやプロセスを見逃しているのかもしれません。おそらく、私が歩いてペイロールするだけでこの給与をXに設定できないと言う理由があるからです。
人々がゲッターとセッターを使用すると、このプロセスのルールを間違った場所にプッシュする傾向があります。これはドメインからさらに離れています。実世界の例を使用すると、入ってきたランダムな人がこれらの値を取得する許可を持っていると仮定すると給与のようになり、そうでなければ彼はそれらを求めません。ドメインがどうであるかだけでなく、実際にはドメインがどうであるかについて嘘をついています。
原則として、ゲッターとセッターは悪い考えです。フィールドが論理的にインターフェースの一部ではなく、それを非公開にしても問題ありません。それが論理的にインターフェースの一部であり、それを公開した場合、それは問題ありません。しかし、それを非公開にしてから振り返り、getterとsetterを提供することによって再び効果的に公開すると、コードがより冗長になり、難読化されることを除いて、元の場所に戻ります。
明らかに、例外があります。 Javaでは、インターフェースを使用する必要がある場合があります。 Java標準ライブラリには後方互換性の要件があり、コード品質の通常の測定値を上回るほど極端です。実際には、伝説的ではあるがまれなケースを処理している可能性が高い場合もあります。後で、保存されたフィールドをオンザフライ計算に置き換えます。インターフェイスを壊すことはありません。ただし、これらは例外です。ゲッターとセッターは、特別な理由が必要なアンチパターンです。
フィールドに直接アクセスできるのか、メソッド経由でアクセスできるのかは、それほど重要ではありません。
クラスの不変式(有用なもの)は重要です。また、それらを保存するために、外部から何かを変更できないようにする必要がある場合があります。例えば。幅と高さが別々の正方形のクラスがある場合、それらの1つを変更すると、正方形以外の何かになります。したがって、メソッドchangeSideが必要です。それがRectangleの場合、setter/publicフィールドを持つことができます。しかし、セッターはゼロよりも大きいかどうかをテストします。
そして具体的な言語(Javaなど)は、これらのメソッド(インターフェース)が必要な理由です。メソッドのもう1つの理由は、互換性(ソースとバイナリ)です。そのため、それらを追加する方が簡単で、パブリックフィールドで十分かどうかを検討します。
ところで。公開の最終フィールドを持つ単純な不変値保持クラスを使用するのが好きです。
インターフェイスを同じに保ちながら、内部を何にでも変更したい場合があります。あなたのインターフェースが変わらなければ、あなたがコーディングしたインターフェースは壊れません。あなたはまだあなたが望むようにあなたの内部を変えることができます。
幅と高さをカプセル化するSize
クラスを考えてみましょう。コンストラクターを使用してセッターを削除できますが、Size
で長方形を描画するのにどのように役立ちますか?幅と高さはクラスの内部データではありません。それらはサイズの消費者が利用できる必要がある共有データです。
オブジェクトは、動作と状態、または属性で構成されます。公開された状態がない場合、動作のみが公開されます。
状態なしで、オブジェクトのコレクションをどのようにソートしますか?オブジェクトの特定のインスタンスをどのように検索しますか?コンストラクタのみを使用する場合、オブジェクトに属性の長いリストがあるとどうなりますか?
検証なしでメソッドパラメータを使用することはできません。したがって、書くのは怠惰です。
setMyField(int myField){
this.myField = myField;
}
このように記述すれば、少なくとも検証にセッターを使用する準備ができています。パブリックフィールドよりも優れていますが、かろうじてです。しかし、少なくとも、一貫性のあるパブリックインターフェイスがあり、顧客のコードを壊すことなく、戻って検証ルールを設定できます。
ゲッター、セッター、プロパティ、ミューテーターは、あなたが何をするかを呼び出しますが、それらは必要です。
私のアプローチはこれです-
後でデータを処理することを期待する場合、ゲッター/セッターは正当化されます。また、変更が発生している場合は、データをゲッター/セッターにプッシュすることがよくあります。
POD構造の場合は、スロットを空けておきます。
より抽象的なレベルでは、問題は「誰がデータを管理するか」であり、それはプロジェクトに依存します。
ゲッターとセッターがカプセル化と真のオブジェクト指向に違反している場合、私は真剣に問題を抱えています。
オブジェクトは、あなたがそれを実行するために必要なことを頭に浮かんだことなら何でも表すといつも感じていました。
JavaでMazesを生成するプログラムを書き終えたところで、「Maze Squares」を表すクラスがあります。このクラスには、座標、壁、ブール値などを表すデータがあります。
このデータを変更/操作/アクセスする方法が必要です!ゲッターとセッターなしで何をしますか? Javaはプロパティを使用せず、このクラスにローカルなすべてのデータをパブリックに設定することは、カプセル化とオブジェクト指向の違反です。
ゲッターとセッターの使用が複雑に感じられる場合、問題は言語そのものであり、概念自体ではない可能性があります。
Rubyで記述されたsecondの例のコードは次のとおりです。
class Fridge
attr_accessor :cheese
end
def go_shopping fridge
fridge.cheese += 5
end
Javaのfirstの例によく似ていることに注意してください。ゲッターとセッターがファーストクラスの市民として扱われる場合、それらを使用するのは面倒ではなく、追加された柔軟性は本当の恩恵になることがあります。たとえば、新しい冷蔵庫でチーズのデフォルト値を返すように決定できます。
class Fridge
attr_accessor :cheese
def cheese
@cheese || 0
end
end
もちろん、公開すべきではない多くの変数があります。内部変数をむやみに公開するとコードが悪化しますが、ゲッターやセッターのせいにすることはほとんどできません。