一般的なOOP問題であると考えていても、特にJavaでこの問題に頻繁に遭遇します。つまり、例外を発生させると設計の問題が明らかになります。
String name
フィールドとString surname
フィールドを持つクラスがあるとします。
次に、それらのフィールドを使用して人の完全な名前を作成し、それをある種のドキュメント(請求書など)に表示します。
public void String name;
public void String surname;
public String getCompleteName() {return name + " " + surname;}
public void displayCompleteNameOnInvoice() {
String completeName = getCompleteName();
//do something with it....
}
ここで、名前が割り当てられる前にdisplayCompleteNameOnInvoice
が呼び出された場合にエラーをスローして、クラスの動作を強化したいと思います。いい考えですね。
例外を発生させるコードをgetCompleteName
メソッドに追加できます。しかし、このように私はクラスユーザーとの「暗黙の」契約に違反しています。一般に、値が設定されていない場合、ゲッターは例外をスローすることは想定されていません。 OK、これは単一のフィールドを返さないため、標準のゲッターではありませんが、ユーザーの観点からは、区別が微妙すぎて考えられない場合があります。
OrdisplayCompleteNameOnInvoice
内から例外をスローできます。しかし、そうするためには、name
またはsurname
フィールドを直接テストする必要があります。そうすると、getCompleteName
で表される抽象化に違反します。完全な名前を確認して作成するのはこのメソッドの責任です。他のデータに基づいて、場合によってはsurname
で十分であると判断することさえできます。
So唯一の可能性は、メソッドのセマンティクスをgetCompleteName
からcomposeCompleteName
に変更するようです。これは、より「アクティブな」動作を示唆し、これにより、スローする機能例外。
これはより良い設計ソリューションですか?私は常に、シンプルさと正確さの最適なバランスを探しています。この問題の設計リファレンスはありますか?
名前を割り当てずにクラスの作成を許可しないでください。
DeadMGによって提供された答えはそれをかなり釘付けにしていますが、少し違った言い方をさせてください:無効な状態のオブジェクトを持つことは避けてください。オブジェクトは、それが果たすタスクのコンテキストでは「全体」でなければなりません。またはそれは存在すべきではありません。
したがって、流動性が必要な場合は、 ビルダーパターン のようなものを使用します。または、「実物」とは対照的に、構築中のオブジェクトを個別に具体化したもので、後者は有効な状態であることが保証され、その状態で定義された操作を実際に公開するものです(例:getCompleteName
)。
コンストラクターまたはビルダーの目的は、オブジェクトの状態を、一貫性があり使用可能なものに設定することです。この保証がないと、オブジェクトの不適切に初期化された状態で問題が発生します。この問題は、並行性を処理していて、オブジェクトの設定が完全に完了する前に何かがオブジェクトにアクセスできる場合に、さらに複雑になります。
したがって、この部分の問題は、「なぜ名前や姓をnullにすることを許可しているのですか」ということです。それがクラスで正しく機能するために有効なものでない場合は、許可しないでください。オブジェクトの作成を適切に検証するコンストラクターまたはビルダーを用意し、正しくない場合はその時点で問題を発生させます。また、 _@NotNull
_アノテーション のいずれかを使用して、「これはnullにできない」ことを伝え、コーディングと分析に適用することもできます。
この保証が整っていると、コードとその機能について推論するのがはるかに簡単になり、奇妙な場所で例外をスローしたり、ゲッター関数を過度にチェックしたりする必要がなくなります。
この問題に関してはかなりの部分があります。 ゲッター と ゲッターとセッター内で何を許可する必要がありますか? ここから スタックオーバーフローで追加のロジック を実行するゲッターとセッター。これは、クラスの設計で何度も発生する問題です。
この核心は、
一般に、ゲッターは、値が設定されていない場合、例外をスローすることは想定されていません
そして、あなたは正しいです。彼らはすべきではない。彼らは世界のほかの人に「愚か」に見えるべきであり、期待されることをするべきです。そこに論理を入れすぎると、最小の驚きの法則に違反する問題が発生します。それに直面してみましょう、あなたは本当にゲッターを_try catch
_でラップしたくないのです。
must JavaBeansフレームワークなどのgetFoo
メソッドを使用する場合や、 [〜#〜] el [〜#〜 ] Beanを取得することを期待して呼び出す(つまり、_<% bar.foo %>
_は実際にクラスでgetFoo()
を呼び出します-脇に設定して、ビュー?」
また、特定のゲッターがインターフェイスで指定されていたり、リファクタリングされているクラスの以前に公開されていたパブリックAPIの一部だったりする可能性があることも理解してください2つのフィールドに分かれています)。
もっとも推理しやすいことをしてください。このコードの保守には、設計に費やすよりも多くの時間を費やします(ただし、設計に費やす時間が短いほど、保守に費やす時間が長くなります)。理想的な選択は、維持にそれほど時間がかからないような方法で設計することですが、これを何日も考えてはいけません-これが本当に設計上の選択でない限りwill後日、影響の日があります。
ゲッターのセマンティックな純粋さをprivate T foo; T getFoo() { return foo; }
に固執しようとすると、ある時点で問題が発生します。時々、コードはそのモデルに適合せず、その方法でそれを維持しようとするゆがみが意味をなさず、最終的に設計が難しくなります。
設計の理想がコードで望んだ方法で実現できない場合があることを時々受け入れます。ガイドラインはガイドラインであり、束縛ではありません。
私はJava=男ではありませんが、これはあなたが提示した両方の制約を順守しているようです。
名前が初期化されていない場合、getCompleteName
は例外をスローしませんが、displayCompleteNameOnInvoice
は例外をスローします。
public String getCompleteName()
{
if (name == null || surname == null)
{
return null;
}
return name + " " + surname;
}
public void displayCompleteNameOnInvoice()
{
String completeName = getCompleteName();
if (completeName == null)
{
//throw an exception.
}
//do something with it....
}
質問に誰も答えていないようです。
人々が信じたいものとは異なり、「無効なオブジェクトを回避する」は実際的な解決策ではありません。
答えは:
簡単な例: FileStream.Length
in C#/。NET 、ファイルがシークできない場合はNotSupportedException
をスローします。
チェック名全体が逆さまになっています。
getCompleteName()
は、どの関数がそれを利用するかを知る必要はありません。したがって、displayCompleteNameOnInvoice()
を呼び出すときにname
がないことは、getCompleteName()
sの責任ではありません。
ただし、name
はdisplayCompleteNameOnInvoice()
の明確な前提条件です。したがって、状態をチェックする責任を負う必要があります(またはdelegate別のメソッドをチェックする責任があります)。