web-dev-qa-db-ja.com

メソッドの過負荷を回避するにはどうすればよいですか?

アプリケーションのソースコードにはかなりの数の場所があり、1つのクラスには同じ名前と異なるパラメーターを持つ多くのメソッドがあります。これらのメソッドには、常に「前の」メソッドのすべてのパラメーターに加えて、もう1つのパラメーターがあります。

これは長い進化(レガシーコード)とこの考え方(私は信じています)の結果です:

Aを実行するメソッドMがあります。A+ Bを実行する必要があります。OK、わかっています... Mに新しいパラメーターを追加し、そのための新しいメソッドを作成します、コードをMからもう1つのパラメーターを持つ新しいメソッドに移動し、そこでA + Bを実行し、新しいパラメーターのデフォルト値を使用してMから新しいメソッドを呼び出します。 "

次に例を示します(Javaのような言語)。

_class DocumentHome {

  (...)

  public Document createDocument(String name) {
    // just calls another method with default value of its parameter
    return createDocument(name, -1);
  }

  public Document createDocument(String name, int minPagesCount) {
    // just calls another method with default value of its parameter
    return createDocument(name, minPagesCount, false);
  }

  public Document createDocument(String name, int minPagesCount, boolean firstPageBlank) {
    // just calls another method with default value of its parameter
    return createDocument(name, minPagesCount, false, "");
  }

  public Document createDocument(String name, int minPagesCount, boolean firstPageBlank, String title) {
    // here the real work gets done
    (...)
  }

  (...)
}
_

これは間違っているように感じます。このような新しいパラメーターを永久に追加し続けることができないだけでなく、メソッド間のすべての依存関係のため、コードを拡張/変更するのが困難です。

これをより良くするためのいくつかの方法を次に示します。

  1. パラメータオブジェクトを導入します。

    _class DocumentCreationParams {
    
      String name;
      int minPagesCount;
      boolean firstPageBlank;
      String title;
    
      (...)
    }
    
    class DokumentHome {
    
      public Document createDocument(DocumentCreationParams p) {
        // here the real work gets done
        (...)
      }
    }
    _
  2. createDocument()を呼び出す前に、パラメータをDocumentHomeオブジェクトに設定します

    _  @In
      DocumentHome dh = null;
    
      (...)
    
      dh.setName(...);
      dh.setMinPagesCount(...);
      dh.setFirstPageBlank(...);
    
      Document newDocument = dh.createDocument();
    _
  3. 作業をさまざまなメソッドに分け、必要に応じて呼び出します。

    _  @In
      DocumentHome dh = null;
    
      Document newDocument = dh.createDocument();
      dh.changeName(newDocument, "name");
      dh.addFirstBlankPage(newDocument);
      dh.changeMinPagesCount(new Document, 10);
    _

私の質問:

  1. 説明されている問題は本当に問題なのですか?
  2. 提案された解決策についてどう思いますか?どちらを好みますか(経験に基づいて)?
  3. あなたは他の解決策を考えることができますか?
16
Ytus

多分 ビルダーパターン を試してみてください? (注:かなりランダムなGoogleの結果:)

var document = new DocumentBuilder()
                   .FirstPageBlank()
                   .Name("doc1final(2).doc")
                   .MinimumNumberOfPages(4)
                   .Build();

提供するオプションよりもビルダーを選ぶ理由を完全に説明することはできませんが、多くのコードに大きな問題があることがわかりました。メソッドに3つ以上のパラメーターが必要だと思われる場合は、コードの構造が間違っている可能性があります(1つはそれを主張します!)。

Paramsオブジェクトの問題は、(作成したオブジェクトが何らかの方法で実際のものでない限り)問題を1つ上のレベルにプッシュするだけであり、無関係なパラメーターのクラスターが「オブジェクト」を形成することになります。

あなたの他の試みは、ビルダーパターンに手を伸ばしているのに、そこに到達していない誰かのように私に見えます:)

20
Froome

パラメータオブジェクトの使用は、メソッドの(過度の)オーバーロードを回避するための良い方法です。

  • コードをクリーンアップします
  • 機能からデータを分離する
  • コードをより保守しやすくする

しかし、私はそれで行き過ぎないようにします。

あちこちにオーバーロードがあることは悪いことではありません。プログラミング言語でサポートされているので、利用してください。

私はビルダーパターンに気づいていませんでしたが、「偶然」に何度か使用しました。ここでも同じことが言えます。無理しないでください。あなたの例のコードはそれから利益を得るでしょう、しかし、単一のオーバーロードメソッドを持つすべてのメソッドのためにそれを実装するのに多くの時間を費やすことはあまり効率的ではありません。

ちょうど私の2セント。

1
Rob

正直なところ、コードに大きな問題はありません。 C#およびC++では、オプションのパラメーターを使用できますが、これは代替手段ですが、私が知る限り、Javaはその種のパラメーターをサポートしていません。

C#では、すべてのオーバーロードをプライベートにでき、オプションのパラメーターを持つ1つのメソッドは、ものを呼び出すためにパブリックです。

あなたの質問のパート2に答えるために、パラメーターオブジェクトまたはディクショナリー/ハッシュマップを使用します。

そのようです:

class DokumentHome {

  public Document createDocument(Map<string, object> params) {
    if (params.containsKey("yourkey")) {
       // do that
    }
    // here the real work gets done
    (...)
  }
}

免責事項として、私は最初にC#およびJavaScriptプログラマー、次にJavaプログラマーです。

0
Knerd

これはビルダーパターンの良い候補だと思います。ビルダーパターンは、同じタイプで異なる表現のオブジェクトを作成する場合に役立ちます。

あなたの場合、私は次のメソッドを持つビルダーを持っています:

SetTitle (Document document) { ... }
SetFirstPageBlank (Document document) { ... }
SetMinimumPageCount (Document document) { ... }

その後、次の方法でビルダーを使用できます。

_builder.SetTitle(document);
_builder.SetFirstPageBlank(document);

余談ですが、私はいくつかの単純なオーバーロードを気にしません。一体何を.NETフレームワークがHTMLヘルパーであらゆる場所で使用していますか。ただし、すべてのメソッドに3つ以上のパラメーターを渡す必要がある場合は、私が何をしているかを再評価します。

0
CodeART

builderソリューションはほとんどのシナリオで機能すると思いますが、より複雑なケースではビルダーも complex to setup になります。どのメソッドを公開する必要があるかなどです。したがって、私たちの多くは最も単純なソリューションを好むでしょう。

単純なビルダーを作成してドキュメントを作成し、このコードをアプリケーションのさまざまな部分(クラス)に拡散するだけの場合、次のことは困難です。

  • organize:多くのクラスがさまざまな方法でドキュメントを作成します
  • maintain:ドキュメントのインスタンス化に対する変更(新しい必須フィールドの追加など)により、 Shotgun Surgery
  • test:ドキュメントを構築するクラスをテストする場合、ドキュメントのインスタンス化を満たすためだけにボイラープレートコードを追加する必要があります。

しかし、これはOPの質問には答えません。

オーバーロードの代替手段

いくつかの選択肢:

  • メソッド名を変更します:同じメソッド名が混乱を招く場合は、メソッドの名前を変更して、createDocumentよりもわかりやすい名前を作成します。たとえば、createLicenseDriveDocumentcreateDocumentWithOptionalFieldsなど。もちろん、これは巨大なメソッド名につながる可能性があるため、これがすべての場合の解決策とは限りません。
  • 静的メソッドを使用します。このアプローチは、上記の最初の選択肢と比較すると、一種の類似しています。それぞれのケースに意味のある名前を使用して、Documentの静的メソッドからドキュメントをインスタンス化できます(例:Document.createLicenseDriveDocument())。
  • 共通のインターフェースを作成しますcreateDocument(InterfaceDocument interfaceDocument)と呼ばれる単一のメソッドを作成し、InterfaceDocumentに異なる implementations を作成できます。例:createDocument(new DocumentMinPagesCount("name"))。もちろん、ケースごとに単一の実装は必要ありません。各実装で複数のコンストラクタを作成し、その実装で意味のあるいくつかのフィールドをグループ化できるためです。このパターンはテレスコープコンストラクターと呼ばれます。

または過負荷ソリューションをそのまま使用します。醜い解決策であっても、それを使用することには多くの欠点はありません。その場合、ドキュメントを作成する必要があるクラスへの依存関係として挿入できるDocumentoFactoryのように、分離したクラスでオーバーロードメソッドを使用することを好みます。私はフィールドを整理して検証できます 複雑さなしで 優れたビルダーを作成し、コードを1か所で保守します。

0
Dherik