どうすれば引数の数を低く保ちながら、サードパーティの依存関係を分離したままにできますか?


13

サードパーティのライブラリを使用しています。彼らは私たちの意図と目的のために、おそらく次のように実装されたPOJOを渡します:

public class OurData {
  private String foo;
  private String bar;
  private String baz;
  private String quux;
  // A lot more than this

  // IMPORTANT: NOTE THAT THIS IS A PACKAGE PRIVATE CONSTRUCTOR
  OurData(/* I don't know what they do */) {
    // some stuff
  }

  public String getFoo() {
    return foo;
  }

  // etc.
}

APIのカプセル化や単体テストの促進など、さまざまな理由で、データをラップしたいと考えています。ただし、コアクラスがデータに依存することは望ましくありません(繰り返しますが、テストのため)。だから今、私はこのようなものを持っています:

public class DataTypeOne implements DataInterface {
  private String foo;
  private int bar;
  private double baz;

  public DataTypeOne(String foo, int bar, double baz) {
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
  }
}

public class DataTypeTwo implements DataInterface {
  private String foo;
  private int bar;
  private double baz;

  public DataTypeOne(String foo, int bar, double baz, String quux) {
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
    this.quux = quux;
  }
}

そして、これ:

public class ThirdPartyAdapter {
  public static makeMyData(OurData data) {
    if(data.getQuux() == null) {
      return new DataTypeOne(
        data.getFoo(),
        Integer.parseInt(data.getBar()),
        Double.parseDouble(data.getBaz()),
      );
    } else {
      return new DataTypeTwo(
        data.getFoo(),
        Integer.parseInt(data.getBar()),
        Double.parseDouble(data.getBaz()),
        data.getQuux();
      );
  }
}

このアダプタークラスは、サードパーティAPIについて知っている必要がある他のいくつかのクラスと結合されており、システムの残りの部分での普及を制限しています。しかし...このソリューションはグロスです!Clean Code、40ページで:

3つ以上の引数(polyadic)には非常に特別な正当化が必要です-とにかく使用すべきではありません。

私が検討したこと:

  • 静的ヘルパーメソッドではなくファクトリオブジェクトを作成する
    • 数え切れないほどの引数を持つ問題を解決しません
  • 依存コンストラクタを持つDataTypeOneおよびDataTypeTwoのサブクラスを作成する
    • ポリアド保護されたコンストラクターがまだあります
  • 同じインターフェースに準拠する完全に別個の実装を作成する
  • 上記のアイデアの複数

この状況はどのように処理する必要がありますか?


これは腐敗防止層ではないことに注意してください状況で。APIに問題はありません。問題は次のとおりです。

  • 私のデータ構造にしたくない import com.third.party.library.SomeDataStructure;
  • テストケースでデータ構造を構築できません
  • 私の現在のソリューションでは、非常に多くの引数が発生します。データ構造を渡さずに、引数の数を少なくしたい。
  • その質問は「どのような反腐敗層です?」。私の質問は、「このシナリオを解決するために、パターンをどのように使用できますか?」です。

私もコードを求めていません(そうでなければ、この質問はSOになります)、コードを効果的に書くことができるように十分な答えを求めます(この質問は提供しません)。


そのようなサードパーティのPOJOが複数ある場合、いくつかの規則(たとえば、キーにint_barという名前)を使用してマップを使用するカスタムテストコードをテスト入力として作成する価値があります。または、JSONまたはXMLをカスタムの中間コードとともに使用します。実際には、com.thirdpartyをテストするためのDSLのようなものです。
user949300

クリーンコードからの完全な引用:The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification — and then shouldn’t be used anyway.
リリエンタール

11
パターンまたはプログラミングガイドラインへの盲目的な順守は、独自のアンチパターンです。
リリエンタール

2
「APIをカプセル化し、ユニットテストを容易にする」このような音は、過剰なテストやテストによって引き起こされるデザインの損傷の場合です(または、最初からこれを別の方法でデザインできることを示しています)。これを自問してみてください。これにより、コードの理解、変更、再利用が本当に簡単になりますか?私はお金を「いいえ」にしたいと思いました。このライブラリを交換する可能性はどれほど現実的ですか?おそらくそうではありません。交換した場合、これによりまったく異なるものをドロップするのが本当に簡単になりますか?繰り返しますが、私は「いいえ」に賭けます。
jpmc26

1
@JamesAndersonおもしろいと思ったので引用を完全に再現しましたが、スニペットからは一般的な関数を指すのかコンストラクターを指すのか明確ではありませんでした。私はこの主張を支持するつもりはありませんでした。jpmc26が言ったように、私の次のコメントは私がそうしていないことを示すものです。なぜ学者を攻撃する必要があると感じるのかわかりませんが、多音節を使用しても、雲の上にある象牙の塔に腰掛けている学者エリート主義者にはなりません。
リリエンタール

回答:


10

いくつかの初期化パラメーターがあるときに使用した戦略は、初期化用のパラメーターのみを含む型を作成することです

public class DataTypeTwoParameters {
    public String foo;  // use getters/setters instead if it's appropriate
    public int bar;
    public double baz;
    public String quuz;
}

次に、DataTypeTwoのコンストラクターはDataTypeTwoParametersオブジェクトを受け取り、DataTypeTwoは次のように構築されます。

DataTypeTwoParameters p = new DataTypeTwoParameters();
p.foo = "Hello";
p.bar = 4;
p.baz = 3;
p.quuz = "World";

DataTypeTwo dtt = new DataTypeTwo(p);

これにより、DataTypeTwoに入力されるすべてのパラメーターとその意味を明確にする多くの機会が与えられます。また、DataTypeTwoParametersコンストラクターで適切なデフォルトを指定して、設定する必要がある値のみを、APIのコンシューマーが好きな順序で設定できるようにすることもできます。


興味深いアプローチ。関連するものをどこに置きますInteger.parseIntか?セッター内、またはパラメータークラス外?
durron597

5
パラメータクラス外。パラメータクラスは「ダム」オブジェクトである必要があり、必要な入力とそのタイプが何であるかを表現する以外のことをしようとしてはなりません。解析は次のように他の場所で行う必要がありますp.bar = Integer.parseInt("4")
エリック

7
これはパラメーターオブジェクトパターンのように聞こえます
-gnat

9
...またはアンチパターン。
テラスティン

1
...または単にに名前DataTypeTwoParametersを変更できますDataTypeTwo
user253751

14

ここには、2つの個別の懸念があります。APIをラップすることと、引数の数を少なく保つことです。

APIをラップするときのアイデアは、要件以外は何も知らないように、ゼロからインターフェイスを設計することです。テスト容易性、施工、一つのオブジェクトでは、あまりにも多くのパラメータ、などあなたがAPI書く:あなたは何も間違っているが、そのAPIと間違って物事の数を同じ息の一覧で、そのAPIであり言う希望あなたが持っていたが。1つではなく複数のオブジェクトが必要な場合は、それを実行します。POJO を作成するオブジェクトに1レベル上位にラップする必要がある場合は、それを行います。

その後、目的のAPIを取得したら、パラメーターカウントはもう問題になりません。そうである場合、考慮すべき一般的なパターンがいくつかあります。

  • Erikの答えのようなパラメーターオブジェクト。
  • Builderパターンあなたは別のビルダーオブジェクトを作成し、その後、あなたの最後のオブジェクトを作成し、個別のパラメータを設定するセッターの番号に電話。
  • Prototypeパターンすでに内部で設定されたフィールドを持つご希望のオブジェクトのサブクラスのクローンを作成、。
  • すでに慣れ親しんでいる工場。
  • 上記のいくつかの組み合わせ。

多くの場合、これらの作成パターンはポリアドリックコンストラクターを呼び出すことになります。これは、カプセル化されている場合は問題ないと見なす必要があります。polyadicコンストラクターの問題は、それらを1回呼び出すことではなく、オブジェクトを構築する必要があるたびにそれらを呼び出すことを余儀なくされることです。

通常OurData、内部を再実装しようとするよりも、オブジェクトへの参照を保存し、メソッド呼び出しを転送することで、基礎となるAPIにパススルーする方がはるかに簡単で維持しやすいことに注意してください。例えば:

public class DataTypeTwo implements DataInterface {
  private OurData data;

  public DataTypeOne(OurData data) {
    this.data = data;
  }

   public String getFoo() {
    return data.getFoo();
  }

  public int getBar() {
    return Integer.parseInt(data.getBar());
  }
  ...
}

この回答の前半:すばらしい、非常に役立つ、+ 1。この答えの後半:「OurDataオブジェクトへの参照を保存することにより、基礎となるAPIにパススルーする」-これは、少なくとも基本クラスでは、依存関係がないことを保証するために回避しようとしています。
durron597

1
そのため、の実装の1つでのみそれを行いますDataInterface。モックオブジェクトの別の実装を作成します。
カールビーレフェルト

@ durron597:はい、しかしあなたは本当にそれがあなたを困らせるなら、あなたはすでにその問題を解決する方法を知っています。
ドックブラウン

1

ボブおじさんの勧告を厳密に解釈しすぎているのではないかと思います。ロジックやメソッド、コンストラクターなどを備えた通常のクラスの場合、ポリアドリックコンストラクターは実際にコードの匂いによく似ています。しかし、厳密にはフィールドを公開するデータコンテナーであり、本質的に既にFactoryオブジェクトであるものによって生成されるものについては、それほど悪くないと思います。

あなたができるコメントで示唆されているようにあなたのためにこれらのコンストラクタのパラメータをラップすることができ、パラメータオブジェクトパターンを使用し、どのようなあなたのローカル・データ・タイプのラッパーがあることで、すでに、基本的に、Parameterオブジェクトを。パラメーターオブジェクトが行うことは、パラメーターをパックし(どのように作成しますか?ポリアドリックコンストラクターを使用しますか?)、それらを1秒後にアンパックして、ほぼ同一のオブジェクトにします。

フィールドのセッターを公開して呼び出したくない場合は、明確に定義されカプセル化されたファクトリー内のポリアドリックコンストラクターに固執するのが良いと思います。


問題は、データ構造内のフィールドの数が複数回変更され、おそらく再び変更されることです。つまり、すべてのテストケースでコンストラクタをリファクタリングする必要があります。賢明なデフォルトのパラメータパターンは、より良い方法のように聞こえます。不変のフォームに保存される可変バージョンを使用すると、多くの点で私の生活が楽になります。
durron597
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.