引数またはメンバー変数としてのメソッドフラグ?


8

タイトルは「メソッドフラグを引数として、またはメンバー変数として?」最適ではないかもしれませんが、より良い用語のATMがないため、次のようになります。

私は現在、特定のクラス(プライベート)メソッドのフラグを関数の引数として渡すか、メンバー変数を介して渡すか、この側面をカバーするパターンまたは名前があるかどうか、および/またはこの問題を回避しようとしています。これが他の設計上の問題を示唆しているかどうか。

例(言語はC ++、Java、C#である可能性があり、実際にはIMHOには関係ありません):

class Thingamajig {
  private ResultType DoInternalStuff(FlagType calcSelect) {
    ResultType res;
    for (... some loop condition ...) {
      ...
      if (calcSelect == typeA) {
        ...
      } else if (calcSelect == typeX) {
        ...
      } else if ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker(FlagType calcSelect) {
    ...
    DoInternalStuff(calcSelect);
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    InternalStuffInvoker(typeA);
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    ResultType x = DoInternalStuff(typeX);
    ... some more code ... further process x ...
    return x;
  }
}

上記では、メソッドInternalStuffInvokerがこの関数内ではまったく使用されない引数を取り、他のプライベートメソッドにのみ転送されることがわかりDoInternalStuffます。(DoInternalStuffこのクラスの他の場所、たとえばDoThatStuff(public)メソッドでプライベートに使用される場所。)

別の解決策は、この情報を運ぶメンバー変数を追加することです:

class Thingamajig {
  private ResultType DoInternalStuff() {
    ResultType res;
    for (... some loop condition ...) {
      ...
      if (m_calcSelect == typeA) {
        ...
      } ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker() {
    ...
    DoInternalStuff();
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    m_calcSelect = typeA;
    InternalStuffInvoker();
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    m_calcSelect = typeX;
    ResultType x = DoInternalStuff();
    ... some more code ... further process x ...
    return x;
  }
}

特に、内部メソッドのセレクターフラグが外部で選択されているディープコールチェーンでは、メンバー変数を使用すると、パススルーパラメーターを渡す必要がないため、中間関数をよりクリーンにすることができます。

一方、このメンバー変数は実際にはオブジェクトの状態を表していません(外部では設定も利用もできないため)が、実際には「内部」プライベートメソッドの隠された追加の引数です。

各アプローチの長所と短所は何ですか?


1
フラグを渡す必要がある場合、おそらく複数の機能を実行する多数の関数があると考えてください。フラグで制御を難読化する必要がないように、制御の流れを整理することを検討する場合があります。
cHao

「DoInternalStuff」と「InternalStuff invoker」は本当にひどい名前です。:)ええ、私はそれが単なる例であることを知っていますが、それはちょっと不吉です。実際のコードが「内部的なもの」のように曖昧なものを実行する場合、それはより大きな問題です。それを整理すると、制御フローを簡略化するいくつかの方法は、それ自体を明らかにする可能性が高くなります。
cHao

1
@cHaoに同意します。過去にこのような状況があった場合、頻繁に渡されるオプションフラグは、単一責任の原則に違反しているというコードの匂いでした。
Bevan

回答:


15

一方、このメンバー変数は実際にはオブジェクトの状態を表していません(外部では設定も利用もできないため)が、実際には「内部」プライベートメソッドの隠された追加の引数です。

それはあなたの質問を読んだときの私の直感を補強します。このデータをできるだけローカルに保つようにしてください。つまり、オブジェクトの状態に追加しないでください。これにより、クラスの状態空間が減少し、コードの理解、テスト、保守がより簡単になります。言うまでもなく、共有状態を回避すると、クラスのスレッドセーフ性も高まります。これは、現時点では問題にならないかもしれませんが、将来的には大きな違いが生じる可能性があります。

もちろん、そのようなフラグを過度に回避する必要がある場合は、迷惑になる可能性があります。この場合は、

  • 関連するパラメーターを1つのオブジェクトにグループ化するパラメーターオブジェクトを作成する、および/または
  • この状態と関連ロジックを別の戦略ファミリにカプセル化します。これの実行可能性は言語に依存します。Javaでは、各戦略ごとに個別のインターフェイスと(おそらく匿名)実装クラスを定義する必要がありますが、C ++またはC#では、関数ポインター、デリゲート、またはラムダを使用してコードを簡素化できます。かなり。

戦略/ラムダアプローチについて言及していただきありがとうございます。物事を裏返しにする必要があるときのために、私はその解決策を維持する必要があります:-)
Martin Ba

6

私は間違いなく「引数」オプションに投票します-私の理由:

  1. 同期-メソッドが複数のスレッドから同時に呼び出された場合はどうなりますか?フラグを引数として渡すことで、これらのフラグへのアクセスを同期させる必要がなくなります。

  2. テスト容易性-クラスの単体テストを作成しているとしましょう。メンバーの内部状態をどのようにテストしますか?コードが例外をスローし、メンバーが予期しない状態で終了するとどうなりますか?

  3. 問題の分離-メソッドの引数にフラグを追加することにより、他のメソッドがフラグを使用することは想定されていないことは明らかです。他の方法で誤って使用することはありません。


3

最初の選択肢は私には問題ないようです。関数はパラメーター付きで呼び出され、そのパラメーターに依存する何かを実行します。パラメータを別の関数に転送するだけでも、結果は何らかの形でパラメータに依存すると予想して実行します。

2番目の選択肢は、アプリケーションスコープではなくクラススコープ内で、グローバル変数を使用するような感じです。しかし、これは同じ問題です。クラスコード全体を注意深く読んで、誰がいつこれらの値を読み書きしているのかを判断する必要があります。

したがって、最初の選択肢が優先されます。

あなたが使用する場合は、あまりにも多くのパラメータを(読み:2以上)の全てのすぐ隣機能に一緒に渡される最初の選択肢、で、それはに関連しない場合、あなたは、それを内部クラスを作る(1つのオブジェクトにそれらを置くことを検討することができますアプリケーションの残りの部分)およびこのオブジェクトをパラメーターとして使用します。


1

「どちらでもない」と言いたい。

まず、に電話をかけるときに何をしようとしているのすでにわかっていることを考慮してくださいDoInternalStuff。旗は同じくらい言う。つまり、先に進んでそれを実行する代わりに、何をすべきかを決定する必要がある関数の呼び出しを回避しているのです。

関数に何をすべきかを伝えたい場合は、何をすべきかを関数に伝えます。各反復で実行する実際の関数をC#デリゲート(またはC ++関数オブジェクト、Javaランナブルなど)として渡すことができます。

C#の場合:

class Thingamajig {
  private delegate WhateverType Behavior(ArgsType args);

  private ResultType DoInternalStuff(Behavior behavior) {
    ResultType res;
    for (... some loop condition ...) {
      ...
      var subResult = behavior(someArgs);
      ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker(Behavior behavior) {
    ...
    DoInternalStuff(behavior);
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    InternalStuffInvoker(doThisStuffPerIteration);
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    ResultType x = DoInternalStuff(doThatStuffPerIteration);
    ... some more code ... further process x ...
    return x;
  }
}

引数はあちこちに渡されてしまいますが、if / elseの大部分はで取り除かれDoInternalStuffます。必要に応じて、デリゲートをフィールドとして保存することもできます。

todo-thingieを渡すか、保存するかについては、1つを選択する必要がある場合は、渡してください。スレッドの安全性をほとんど無視しているため、保管すると後でお尻に食い込む可能性があります。スレッドで何かをするつもりなら、今度はループの全期間ロックする必要があります。そうしないと、誰かがtodoをあなたの下から直接切り替えることができます。これを渡すと、ごみが出る可能性が低くなります。ロックする必要がある場合は、ループ全体ではなく、1回の反復の一部で実行できます。

率直に言って、それが煩わしいのは良いことです。おそらく、あなたがやりたいことを行うためのもっと簡単な方法があるでしょう。コードは明らかに本物ではないので、アドバイスはできませんでした。最良の答えはケースごとに異なります。

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