時折、多くの引数を受け取るメソッドを書く必要があります。例えば:
public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}
この種の問題が発生すると、引数をマップにカプセル化することがよくあります。
Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;
......
public void doSomething(Map<Object,Object> params)
{
// extracting params
Object objA = (Object)params.get("objA");
......
}
これは良い方法ではありません。paramsをマップにカプセル化することは効率の無駄です。良いことは、クリーンな署名であり、他のパラメーターを最小限の変更で簡単に追加できることです。この種の問題のベストプラクティスは何ですか?
Effective Java 、Chapter 7(Methods)、Item 40(Design method signatures入念に設計)では、Blochはこう書いています:
過度に長いパラメーターリストを短縮する方法は3つあります。
詳細については、本を購入することをお勧めします。本当に価値があります。
魔法の文字列キーでマップを使用するのは悪い考えです。コンパイル時のチェックが失われ、必要なパラメーターが何であるかが本当にわかりません。それを補うために非常に完全なドキュメントを書く必要があります。コードを見ずに、これらの文字列が何であるかを数週間で覚えていますか?タイプミスをした場合はどうなりますか?間違ったタイプを使用しますか?コードを実行するまでわかりません。
代わりにモデルを使用してください。これらすべてのパラメーターのコンテナーになるクラスを作成します。そうすれば、Javaの型安全性を維持できます。そのオブジェクトを他のメソッドに渡したり、コレクションに入れたりすることもできます。
もちろん、パラメーターのセットが他の場所で使用されたり、渡されたりしない場合、専用のモデルは過剰である可能性があります。バランスを取る必要があるため、常識を使用してください。
多くのオプションパラメータがある場合は、流れるようなAPIを作成できます。単一のメソッドをメソッドのチェーンで置き換えます
exportWithParams().datesBetween(date1,date2)
.format("xml")
.columns("id","name","phone")
.table("angry_robots")
.invoke();
静的インポートを使用すると、内部の流れるようなAPIを作成できます。
... .datesBetween(from(date1).to(date2)) ...
「パラメーターオブジェクトの導入」と呼ばれます。複数の場所で同じパラメーターリストを渡す場合は、それらすべてを保持するクラスを作成してください。
XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);
同じパラメーターリストをあまり頻繁に渡さない場合でも、その簡単なリファクタリングにより、コードの可読性が向上します。これはalways goodです。 3か月後にコードを見ると、バグの修正や機能の追加が必要なときに理解しやすくなります。
もちろんこれは一般的な哲学であり、詳細を提供していないため、詳細なアドバイスもできません。 :-)
まず、メソッドをリファクタリングしようとします。多数のパラメーターを使用している場合は、いずれにしても長すぎる可能性があります。分解すると、コードが改善され、各メソッドのパラメーターの数が減る可能性があります。また、操作全体を独自のクラスにリファクタリングできる場合もあります。次に、同じパラメーターリストの同じ(またはスーパーセット)を使用している他のインスタンスを探します。複数のインスタンスがある場合、これらのプロパティが一緒に属していることを示す可能性があります。その場合、パラメーターを保持するクラスを作成して使用します。最後に、パラメーターの数がマップオブジェクトを作成してコードの可読性を向上させる価値があるかどうかを評価します。これは個人的な呼び出しだと思います-このソリューションにはそれぞれの方法で苦労があり、トレードオフのポイントはどこにあるかが異なります。 6つのパラメーターについては、おそらくそれを行いません。 10の場合、おそらく他の方法が最初に機能しなかった場合です。
多くの場合、これはオブジェクトの構築時に問題になります。
その場合、builder object patternを使用します。パラメーターのリストが大きく、必ずしもすべてのパラメーターが必要なわけではない場合は、うまく機能します。
メソッド呼び出しに適応させることもできます。
また、読みやすさも大幅に向上します。
public class BigObject
{
// public getters
// private setters
public static class Buider
{
private A f1;
private B f2;
private C f3;
private D f4;
private E f5;
public Buider setField1(A f1) { this.f1 = f1; return this; }
public Buider setField2(B f2) { this.f2 = f2; return this; }
public Buider setField3(C f3) { this.f3 = f3; return this; }
public Buider setField4(D f4) { this.f4 = f4; return this; }
public Buider setField5(E f5) { this.f5 = f5; return this; }
public BigObject build()
{
BigObject result = new BigObject();
result.setField1(f1);
result.setField2(f2);
result.setField3(f3);
result.setField4(f4);
result.setField5(f5);
return result;
}
}
}
// Usage:
BigObject boo = new BigObject.Builder()
.setField1(/* whatever */)
.setField2(/* whatever */)
.setField3(/* whatever */)
.setField4(/* whatever */)
.setField5(/* whatever */)
.build();
検証ロジックをBuilder set ..()およびbuild()メソッドに配置することもできます。
Parameter object と呼ばれるパターンがあります。
アイデアは、すべてのパラメーターの代わりに1つのオブジェクトを使用することです。後でパラメータを追加する必要がある場合でも、オブジェクトに追加するだけです。メソッドのインターフェースは同じままです。
そのデータを保持するクラスを作成できます。ただし、十分な意味を持つ必要がありますが、マップ(OMG)を使用するよりもはるかに優れている必要があります。
Code Complete *はいくつかのことを提案します:
*初版、私は更新する必要があることを知っています。また、OOPがより一般的になり始めたときに第2版が作成されてから、このアドバイスの一部が変更された可能性があります。
リファクタリングすることをお勧めします。これらのオブジェクトは、このメソッドに渡す必要があることを意味しますか?それらを単一のオブジェクトにカプセル化する必要がありますか?
マップを使用することは、コールシグネチャを削除する簡単な方法ですが、別の問題があります。メソッドの本体内を見て、メソッドがそのMapで何を期待しているのか、キー名、値のタイプを確認する必要があります。
よりクリーンな方法は、オブジェクトBean内のすべてのパラメーターをグループ化することですが、それでも問題を完全に解決するわけではありません。
ここにあるのは設計上の問題です。メソッドに7つ以上のパラメーターがあると、それらが何を表し、どの順序を持っているかを思い出すのに問題が生じ始めます。ここから、間違ったパラメーターの順序でメソッドを呼び出すだけで、多くのバグを取得できます。
多くのパラメーターを送信するためのベストプラクティスではなく、アプリのより良い設計が必要です。
Beanクラスを作成し、すべてのパラメーター(セッターメソッド)を設定し、このBeanオブジェクトをメソッドに渡します。
コードを見て、これらすべてのパラメーターが渡される理由を確認してください。メソッド自体をリファクタリングできる場合もあります。
マップを使用すると、メソッドが脆弱になります。メソッドを使用している人がパラメーター名のスペルを間違えたり、メソッドがUDTを予期している場所に文字列を投稿したりするとどうなりますか?
Transfer Object を定義します。少なくとも型チェックを提供します。メソッド内ではなく使用時に検証を実行することもできます。
渡されるパラメーターが多すぎる場合は、メソッドをリファクタリングしてみてください。たぶん、想定外のことをたくさんやっているのかもしれません。そうでない場合は、パラメータを単一のクラスに置き換えてみてください。これにより、すべてを単一のクラスインスタンスにカプセル化し、パラメーターではなくインスタンスを渡すことができます。