過剰なメソッドのオーバーロードを避ける方法は?


16

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

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

Aを実行するメソッドMがあります。A+ Bを実行する必要があります。わかりました。新しいパラメータを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. DocumentHome呼び出す前にオブジェクトにパラメーターを設定しますcreateDocument()

      @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. 他の解決策を思いつきますか?

1
どの言語をターゲットにしていますか、それとも単なるジェネレルですか?
ケナード14

特定の言語ではなく、一般的なものです。他の言語の機能がこれにどのように役立つかをお気軽に示してください。
イタス14

ここで言ったように、programmers.stackexchange.com / questions / 235096 / … C#とC ++にはいくつかの機能があります。
ケナード

この質問が、このような種類のメソッドのオーバーロードをサポートするすべての言語に適用されることは明らかです。
Doc Brown 14

1
@DocBrownは問題ありませんが、すべての言語が同じ代替をサポートしているわけではありません;)
Knerd

回答:


20

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

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

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

paramsオブジェクトの問題は(作成するオブジェクトが何らかの形で実際にある場合を除き)、問題を1レベル上に押し上げるだけで、関連しないパラメーターのクラスターが「オブジェクト」を形成することになります。

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


ありがとうございました。あなたの答えは、ソリューションのnumにすることができます。4、はい。私はこの方法でもっと答えを探しています:「私の経験では、これは...につながり、修正することができます...」または「同僚のコードでこれを見たとき、代わりに...を勧めます」
イタス14

私は知りません...あなたはただ問題を動かしているように思えます。たとえば、アプリケーションのさまざまな部分でドキュメントを作成できる場合、これを整理してテストし、このドキュメントの構成を分離したクラスで分離することをお勧めします(aを使用するなどDocumentoFactory)。持つbuilder別の場所にそれは(例ごとに、文書に新しい必須フィールドを追加するなど)文書の建設に関する将来の変化を制御し、ちょうどビルダーを使用しているクラスの必需ビルダードキュメントを満足させるためにテストに余分な定型的なコードを追加するのは難しいです。
デリック

1

パラメーターオブジェクトを使用すると、メソッドの(過剰な)オーバーロードを回避できます。

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

しかし、私はそれで行き過ぎないでしょう。

あちこちで過負荷になっているのは悪いことではありません。プログラミング言語でサポートされているため、あなたの利益のためにそれを使用してください。

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

ちょうど2セントです。


0

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

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

質問のパート2に答えるために、パラメーターオブジェクトまたは辞書/ HashMapを取得します。

そのようです:

class DokumentHome {

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

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


4
その解決策ですが、私はそれが良い解決策だとは思いません(少なくとも、タイプセーフが期待される状況ではそうではありません)。
Doc Brown 14

そのとおり。そのような場合のために、オーバーロードされたメソッドまたはオプションのパラメーターが好きです。
ケナード

2
辞書をパラメーターとして使用すると、メソッドの見かけのパラメーターの数を減らす簡単な方法ですが、メソッドの実際の依存関係がわかりにくくなります。これにより、呼び出し側は、コメント、メソッドへの他の呼び出し、またはメソッド実装自体のいずれであっても、ディクショナリに正確に必要なものを他の場所で探す必要があります。
マイクパートリッジ14

辞書は本当にオプションのパラメーターにのみ使用してください。したがって、基本的なユースケースでは、あまり多くのドキュメントを読む必要はありません。
user949300

0

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

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

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

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

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

余談ですが、単純なオーバーロードはほとんど気にしません。.NETフレームワークは、HTMLヘルパーを使用してあちこちでオーバーロードを使用しています。ただし、3つ以上のパラメーターをすべてのメソッドに渡す必要がある場合は、実行していることを再評価します。


0

builderソリューションはシナリオのほとんどの部分で機能すると思いますが、より複雑なケースでは、ビルダーはセットアップ複雑になります。これは、メソッドの順序、公開する必要があるメソッド、またはしないメソッドなど。だから、私たちの多くは最も単純なソリューションを好むでしょう。

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

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

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

オーバーロードの代替

いくつかの選択肢:

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.