107

場合によっては、多くの引数を受け取るメソッドを作成する必要があります。たとえば、次のようになります。

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");
 ......
 }

これは良い習慣ではありません。パラメータをマップにカプセル化することは、完全に効率の無駄です。良い点は、クリーンな署名であり、変更を最小限に抑えて他のパラメータを簡単に追加できることです。この種の問題のベストプラクティスは何ですか?

4

17 に答える 17

160

効果的なJava 、第7章(メソッド)、アイテム40(メソッドのシグネチャを慎重に設計する)で、Blochは次のように書いています。

過度に長いパラメータリストを短縮するには、次の3つの手法があります。

  • メソッドを複数のメソッドに分割します。各メソッドは、パラメーターのサブセットのみを必要とします。
  • パラメータのグループを保持するヘルパークラスを作成します(通常は静的メンバークラス)
  • Builderパターンをオブジェクト構築からメソッド呼び出しに適合させます。

詳細については、この本を購入することをお勧めします。それだけの価値があります。

于 2010-03-12T12:08:02.607 に答える
76

魔法の文字列キーでマップを使用することは悪い考えです。コンパイル時のチェックが失われ、必要なパラメータが何であるかが本当に不明確になります。あなたはそれを補うために非常に完全なドキュメントを書く必要があるでしょう。コードを見ずに、これらの文字列が何であるかを数週間で覚えていますか?タイプミスをした場合はどうなりますか?間違ったタイプを使用しますか?コードを実行するまでわかりません。

代わりにモデルを使用してください。これらすべてのパラメーターのコンテナーとなるクラスを作成します。そうすれば、Javaの型安全性を維持できます。そのオブジェクトを他のメソッドに渡したり、コレクションに入れたりすることもできます。

もちろん、パラメータのセットが他の場所で使用されていないか、渡されていない場合、専用モデルはやり過ぎかもしれません。バランスを取る必要があるので、常識を働かせてください。

于 2010-03-12T12:01:41.970 に答える
26

オプションのパラメータが多数ある場合は、流暢なAPIを作成できます。単一のメソッドを一連のメソッドに置き換えます

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

静的インポートを使用すると、内部の流暢なAPIを作成できます。

... .datesBetween(from(date1).to(date2)) ...
于 2010-03-13T19:51:35.737 に答える
13

これは「パラメータオブジェクトの紹介」と呼ばれます。複数の場所で同じパラメータリストを渡していることに気付いた場合は、それらすべてを保持するクラスを作成するだけです。

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

同じパラメータリストを頻繁に渡さない場合でも、簡単なリファクタリングによってコードの可読性が向上します。これは常に優れています。3か月後にコードを見ると、バグを修正したり機能を追加したりする必要があるときに、理解しやすくなります。

もちろんそれは一般的な哲学であり、あなたは詳細を提供していないので、私もあなたにこれ以上の詳細なアドバイスを与えることはできません。:-)

于 2010-03-12T12:07:57.767 に答える
10

まず、メソッドをリファクタリングしてみます。その数のパラメータを使用している場合は、いずれにしても長すぎる可能性があります。それを分解すると、コードが改善され、各メソッドのパラメーターの数が減る可能性があります。操作全体を独自のクラスにリファクタリングできる場合もあります。次に、同じパラメータリストの同じ(またはスーパーセット)を使用している他のインスタンスを探します。複数のインスタンスがある場合は、これらのプロパティが一緒に属していることを示している可能性があります。その場合は、パラメータを保持するクラスを作成して使用してください。最後に、パラメーターの数が、コードの可読性を向上させるためにマップオブジェクトを作成する価値があるかどうかを評価します。これは個人的な呼びかけだと思います。このソリューションにはそれぞれの面で苦痛があり、トレードオフポイントが異なる場合があります。6つのパラメーターの場合、私はおそらくそれをしません。10の場合、おそらく(他の方法のどれも最初に機能しなかった場合)。

于 2010-03-12T12:07:10.283 に答える
8

これは、オブジェクトを作成するときにしばしば問題になります。

その場合、ビルダーオブジェクトパターンを使用します。パラメーターのリストが大きく、必ずしもすべてが必要なわけではない場合にうまく機能します。

メソッド呼び出しに適合させることもできます。

また、読みやすさが大幅に向上します。

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()メソッドに検証ロジックを配置することもできます。

于 2014-09-16T20:14:30.393 に答える
7

Parameterオブジェクトと呼ばれるパターンがあります。

アイデアは、すべてのパラメーターの代わりに1つのオブジェクトを使用することです。これで、後でパラメータを追加する必要がある場合でも、オブジェクトに追加するだけで済みます。メソッドインターフェイスは同じままです。

于 2010-03-12T12:05:34.310 に答える
5

そのデータを保持するクラスを作成できます。ただし、十分に意味がある必要がありますが、マップ(OMG)を使用するよりもはるかに優れています。

于 2010-03-12T11:53:21.187 に答える
4

Code Complete *は、いくつかのことを示唆しています。

  • 「ルーチンのパラメーターの数を約7に制限します。7は人々の理解のための魔法の数です」(p108)。
  • 「パラメーターを入力-変更-出力の順序で配置します...複数のルーチンが同様のパラメーターを使用する場合は、同様のパラメーターを一貫した順序で配置します」(p105)。
  • ステータス変数またはエラー変数を最後に置きます。
  • tvanfossonが述べたように、ルーチンが必要とする構造化変数(オブジェクト)の部分のみを渡しますとはいえ、関数で構造化変数のほとんどを使用している場合は、構造全体を渡すだけですが、これによりある程度の結合が促進されることに注意してください。

*初版、私は更新する必要があることを知っています。また、OOPの人気が高まり始めたときに、第2版が作成されてから、このアドバイスの一部が変更された可能性があります。

于 2010-03-15T08:52:08.490 に答える
3

マップの使用は、コールシグネチャをクリーンアップする簡単な方法ですが、別の問題が発生します。メソッドの本体の内部を調べて、メソッドがそのマップで何を期待しているのか、キー名は何か、値のタイプは何かを確認する必要があります。

よりクリーンな方法は、オブジェクトBean内のすべてのパラメーターをグループ化することですが、それでも問題を完全に修正することはできません。

ここにあるのは設計上の問題です。メソッドに7つを超えるパラメーターがあると、それらが何を表し、どのような順序であるかを思い出すのに問題が発生し始めます。ここから、間違ったパラメータの順序でメソッドを呼び出すだけで、多くのバグが発生します。

多くのパラメーターを送信するためのベストプラクティスではなく、アプリのより良い設計が必要です。

于 2010-03-12T12:02:40.383 に答える
2

リファクタリングすることをお勧めします。これらのオブジェクトは、このメソッドに渡す必要があることを意味しますか?それらを単一のオブジェクトにカプセル化する必要がありますか?

于 2010-03-12T11:53:19.970 に答える
1

Beanクラスを作成し、すべてのパラメーター(setterメソッド)を設定して、このBeanオブジェクトをメソッドに渡します。

于 2010-03-12T12:24:19.693 に答える
1
  • コードを見て、これらすべてのパラメーターが渡される理由を確認してください。メソッド自体をリファクタリングできる場合もあります。

  • マップを使用すると、メソッドが脆弱になります。メソッドを使用している誰かがパラメーター名のスペルを間違えたり、メソッドがUDTを予期している場所に文字列を投稿したりした場合はどうなりますか?

  • 転送オブジェクトを定義します。少なくともタイプチェックを提供します。メソッド内ではなく、使用時に検証を実行できる場合もあります。

于 2010-03-15T08:17:42.667 に答える
1

私はあなたが前にそれをした方法に固執すると言うでしょう。あなたの例のパラメータの数は多くはありませんが、代替案ははるかに恐ろしいものです。

  1. マップ-あなたが言及した効率の問題がありますが、ここでのより大きな問題は次のとおりです。

    • 発信者は、他の何かを参照せずに何を送信すればよいかわかりません... 使用されているキーと値を
      正確に示すjavadocsがありますか?
      もしそうなら(これは素晴らしいことです)、たくさんのパラメーターを持つことも問題ではありません。
    • 異なる引数タイプを受け入れることは非常に困難になります。入力パラメーターを単一の型に制限するか、Map <String、Object>を使用してすべての値をキャストすることができます。ほとんどの場合、どちらのオプションもひどいものです。
  2. ラッパーオブジェクト-最初にラッパーオブジェクトを埋める必要があるため、これは問題を移動するだけです-メソッドに直接ではなく、パラメーターオブジェクトのコンストラクターになります。問題の移動が適切かどうかを判断するには、そのオブジェクトを再利用する必要があります。例えば:

使用しない:最初の呼び出しで1回だけ使用されるため、1行を処理するための追加のコードがたくさんあります...?

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

それを使用するかもしれません:ここでは、それはもう少しすることができます。まず、3つのメソッド呼び出しのパラメーターを因数分解できます。それ自体で他の2行を実行することもできます...したがって、ある意味で状態変数になります...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. ビルダーパターン-これは私の見解ではアンチパターンです。最も望ましいエラー処理メカニズムは、後でではなく、早期に検出することです。しかし、ビルダーパターンでは、必須パラメーターが欠落している(プログラマーはそれを含めるとは考えていませんでした)呼び出しは、コンパイル時から実行時に移動されます。もちろん、プログラマーが意図的にnullなどをスロットに入れた場合、それはランタイムになりますが、それでも早期にいくつかのエラーをキャッチすることは、呼び出しているメソッドのパラメーター名を見ることを拒否するプログラマーに対応するためのはるかに大きな利点です。多数のオプションパラメータを処理する場合にのみ適切であると思いますが、それでも、メリットはせいぜいわずかです。私はビルダーの「パターン」に非常に反対しています。

人々が考慮することを忘れている他のことは、これらすべてにおけるIDEの役割です。メソッドにパラメーターがある場合、IDEはほとんどのコードを生成し、何を提供/設定する必要があるかを示す赤い線が表示されます。オプション3を使用すると、これは完全に失われます。今ではそれを正しくするのはプログラマー次第であり、コーディングやコンパイル時に手がかりはありません...プログラマーはそれをテストして見つける必要があります。

さらに、オプション2と3は、不必要に広く採用された場合、大量の重複コードが生成されるため、メンテナンスの面で長期的な悪影響を及ぼします。コードが多ければ多いほど、維持する必要があり、それを維持するためにより多くの時間とお金が費やされます。

于 2020-08-02T17:53:12.890 に答える
0

これは多くの場合、クラスが複数の責任を負っていることを示しています(つまり、クラスの責任が多すぎます)。

単一責任の原則を参照してください

詳細については。

于 2010-03-12T12:31:25.137 に答える
0

渡すパラメーターが多すぎる場合は、メソッドをリファクタリングしてみてください。多分それはそれがすることを想定していない多くのことをしている。そうでない場合は、パラメーターを単一のクラスに置き換えてみてください。このようにして、すべてを単一のクラスインスタンスにカプセル化し、パラメータではなくインスタンスを渡すことができます。

于 2010-03-12T14:55:39.570 に答える
0

...そしてボブはあなたの叔父です:オブジェクト作成のための手間のかからない派手なパンツAPI!

https://projectlombok.org/features/Builder

于 2021-10-04T13:33:17.060 に答える