web-dev-qa-db-ja.com

ゲッターとセッターの内部で何を許可すべきですか?

ゲッターとセッターのメソッドとカプセル化についての興味深いインターネットの議論に入りました。誰かがすべきことは、それらを「純粋」に保ち、カプセル化を確実にするための割り当て(セッター)または変数アクセス(ゲッター)だけであると言いました。

  • これは、そもそもゲッターとセッターを使用する目的を完全に無効にし、検証とその他のロジック(当然ながら奇妙な副作用はありません)を許可する必要がありますか?
  • 検証はいつ行うべきですか?
    • 値を設定するときは、セッター内(オブジェクトが無効な状態にならないようにするため-私の意見)
    • 値を設定する前に、セッターの外
    • オブジェクト内、値が使用される前
  • セッターは値を変更できますか(有効な値を正規の内部表現に変換する可能性があります)?
45
Botond Balázs

大学でC++を学ぶときに、講師と同様の議論をしたことを覚えています。変数をパブリックにできるときにゲッターとセッターを使用する意味がわかりませんでした。私は長年の経験で理解を深め、単に「カプセル化を維持する」と言うよりも理由を学びました。

ゲッターとセッターを定義することにより、一貫性のあるインターフェイスを提供し、実装を変更したい場合に依存するコードを壊す可能性を減らすことができます。これは、クラスがAPIを介して公開され、他のアプリまたはサードパーティによって使用される場合に特に重要です。それで、ゲッターやセッターに入るものはどうですか?

ゲッターは、動作を予測可能にするため、値にアクセスするための単純な単純なパススルーとして実装するのが一般的です。一般的に言っているのは、ゲッターが計算または条件付きコードによって操作された値にアクセスするために使用されたケースを見たことがあるからです。デザイン時に使用するビジュアルコンポーネントを作成している場合は、一般的にはあまり良くありませんが、実行時には便利なようです。ただし、これと単純なメソッドの使用には実際の違いはありません。ただし、メソッドを使用する場合は、一般にメソッドに適切な名前を付け、コードを読み取るときに「ゲッター」の機能がより明確になるようにします。

以下を比較してください:

int aValue = MyClass.Value;

そして

int aValue = MyClass.CalculateValue();

2番目のオプションは、値が計算されていることを明確にしますが、最初の例は、値自体について何も知らずに単に値を返すことを示しています。

次のことがより明確になるとあなたはおそらく主張するでしょう:

int aValue = MyClass.CalculatedValue;

ただし問題は、値が他の場所ですでに操作されていると想定していることです。したがって、ゲッターの場合、値を返すときに何か他のことが起こっていると想定したい場合がありますが、プロパティのコンテキストでそのようなことを明確にすることは難しく、プロパティ名には動詞を含めないでください。そうしないと、使用した名前にアクセスしたときに括弧で装飾する必要があるかどうかを一目で理解することが難しくなります。

ただし、セッターは少し異なるケースです。プロパティに送信されるデータを検証するためにセッターが追加の処理を提供し、値の設定がプロパティの定義された境界に違反する場合は例外をスローすることが完全に適切です。ただし、一部の開発者がセッターに処理を追加する場合の問題は、何らかの方法でデータの計算や操作を行うなど、セッターにもう少し多くのことをさせたいという誘惑が常にあることです。これは、場合によっては予測できない、または望ましくない可能性のある副作用が発生する可能性がある場所です。

セッターの場合、私は常に単純な経験則を適用します。これは、データに対してできる限り少ないことを行うことです。たとえば、通常は境界テストと丸めのどちらかを許可して、必要に応じて両方で例外を発生させるか、不要な例外を避けて賢明に回避できるようにします。浮動小数点プロパティは、例外が発生しないように小数点以下の桁数を丸めながら、範囲内の値を少数の小数点以下の桁数で入力できるようにする場合に適した例です。

セッター入力のある種の操作を適用すると、ゲッターの場合と同じ問題が発生します。これは、単に名前を付けるだけではセッターが何をしているかを他の人に知らせるのが難しいということです。例えば:

MyClass.Value = 12345;

これは、値がセッターに渡されたときに何が起こるかについて何か教えてくれますか?

どうですか:

MyClass.RoundValueToNearestThousand(12345);

2番目の例は、データに何が起こるかを正確に示していますが、最初の例は、値が任意に変更されるかどうかを通知しません。コードを読むとき、2番目の例はその目的と機能がはるかに明確になります。

これは、そもそもゲッターとセッターを使用する目的を完全に無効にし、検証と他のロジック(奇妙な副作用はありません)は許可されるべきだと思いますか?

ゲッターとセッターを使用することは、「純粋さ」を目的としたカプセル化ではなく、クラスのインターフェイスへの変更を危険にさらすことなくコードを簡単にリファクタリングできるようにするためのカプセル化です。検証はセッターでは完全に適切ですが、呼び出しコードが特定の方法で行われる検証に依存している場合、検証を変更すると呼び出しコードとの互換性が失われる可能性があるという小さなリスクがあります。これは一般にまれで比較的リスクの低い状況ですが、完全を期すために注意する必要があります。

検証はいつ行うべきですか?

検証は、実際に値を設定する前に、セッターのコンテキスト内で行う必要があります。これにより、例外がスローされた場合でも、オブジェクトの状態が変化せず、データが無効になる可能性があります。一般的に、セッターコードを整理するために、セッター内で最初に呼び出される別のメソッドに検証を委任する方が良いと思います。

セッターは値を変更できますか(有効な値を正規の内部表現に変換する可能性があります)?

非常にまれなケースではあるかもしれません。一般的には、そうしない方が良いでしょう。これは、別の方法に任せるのが最善の方法です。

38
S.Robins

ゲッター/セッターが単に値をミラーリングする場合、それらを持っていること、または値をプライベートにすることには意味がありません。正当な理由がある場合、一部のメンバー変数をパブリックにすることには何の問題もありません。 3Dポイントクラスを作成している場合、.x、.y、.zをパブリックにすることは完全に理にかなっています。

ラルフ・ウォルド・エマーソンが言ったように、「愚かな一貫性とは、小さな心のホブゴブリンであり、小さな政治家や哲学者、そしてジャワのデザイナーによって崇拝されています。」

ゲッター/セッターは、他の内部変数を更新したり、キャッシュされた値を再計算したり、クラスを無効な入力から保護したりする必要がある副作用がある場合に役立ちます。

内部構造を隠すという通常の正当化は、一般的に最も役に立たない。例えば。私はこれらのポイントを3つの浮動小数点数として保存しているので、リモートデータベースに文字列として保存することにしたので、ゲッター/セッターを非表示にして、呼び出し元のコードに他の影響を与えずにそれを実行できるようにします。

20
Martin Beckett

Meyer's Uniform Access Principle:「モジュールによって提供されるすべてのサービスは、ストレージまたは計算によって実装されているかどうかを裏付けることのない、統一表記によって利用可能である必要があります。」ゲッター/セッター、つまりプロパティの背後にある主な理由です。

クラスの1つのフィールドをキャッシュまたは遅延計算する場合、具体的なデータではなく、公開されたプロパティアクセサーしかない場合はいつでも変更できます。

値オブジェクト、単純な構造ではこの抽象化は必要ありませんが、完全に本格的なクラスでは必要だと思います。

9
Karl

Eiffel言語を通じて導入された、クラスを設計するための一般的な戦略は Command-Query Separation です。アイデアは、メソッドがどちらかがオブジェクトについて何かまたはオブジェクトに何かを行うように伝えますが、両方を行うようには指示しません。

これは、クラスのパブリックインターフェースにのみ関係し、内部表現には関係しません。データベース内の行に裏打ちされたデータモデルオブジェクトについて考えてみましょう。データを読み込まずにオブジェクトを作成し、最初にgetterを呼び出すと、実際にSELECTが実行されます。それは問題ありません。オブジェクトの表現方法に関する内部の詳細を変更している可能性がありますが、そのオブジェクトのクライアントに表示される方法は変更していません。 getterを複数回呼び出しても、異なる結果を返す場合でも、同じ結果を得ることができるはずです。

同様に、セッターは契約上、オブジェクトの状態を変更するだけのように見えます。データベースにUPDATEを書き込むか、パラメータを内部オブジェクトに渡すなど、複雑な方法でそれを行う場合があります。それは問題ありませんが、状態の設定とは無関係のことを行うのは意外です。

Meyer(Eiffelの作成者)も検証について言いたいことがありました。基本的に、オブジェクトが静止しているときは常に、オブジェクトは有効な状態でなければなりません。したがって、コンストラクターが終了した直後、すべての外部メソッド呼び出しの前後(ただし必ずしもそうではない)では、オブジェクトの状態は一貫している必要があります。

その言語では、プロシージャを呼び出すための構文と公開された属性を読み取るための構文はどちらも同じに見えることに注目するのは興味深いことです。つまり、呼び出し元は、メソッドを使用しているか、インスタンス変数を直接使用しているかを判別できません。問題が発生するのは、この実装の詳細を隠さない言語だけです。呼び出し側が判別できない場合は、その変更をクライアントコードに漏らすことなく、パブリックivarとアクセサーを切り替えることができます。

2
user4051

もう1つの受け入れ可能なことは、クローン作成です。いくつかのケースでは、誰かがあなたのクラスを与えた後、例えば、何かのリスト、彼はあなたのクラスの中でそれを変更することはできません(またはそのリストのオブジェクトを変更することはできません)。したがって、セッターでパラメーターのディープコピーを作成し、ゲッターでディープコピーを返します。 (不変の型をパラメーターとして使用することも別のオプションですが、上記はそれが不可能であると想定しています)しかし、必要がない場合は、アクセサーで複製しないでください。プロパティ/ゲッターとセッターを一定のコストの操作と考えるのは簡単ですが(適切ではありません)、これはAPIのユーザーを待っているパフォーマンス地雷です。

1
user470365

プロパティアクセサーとデータを格納するivarの間には、常に1-1のマッピングがあるとは限りません。

たとえば、ビューの中心を格納するivarがない場合でも、ビュークラスはcenterプロパティを提供する場合があります。 centerを設定すると、Origintransformなど、他のivarが変更されますが、クラスのクライアントは知らないか気にしませんhowcenterは、正しく機能することを条件に保存されます。ただし、notが発生するのは、centerを設定すると、新しい値を保存するために必要なすべてのことを超えて発生するということですが、これは完了しています。

1
Caleb

セッターとゲッターの最も優れた点は、APIを変更せずにAPIのルールを簡単に変更できることです。バグを検出した場合は、ライブラリ内のバグを修正して、すべてのコンシューマにコードベースを更新させないほうがはるかに可能性が高くなります。

0
AlexanderBrevig