web-dev-qa-db-ja.com

最善のアプローチ-複数の条件付きif -elseをより便利な設計に変換する

StockResponseという、応答の状態を処理するクラスがあります。コードには、株式の各状態を処理する複数のifがあります。ほとんどの場合、デフォルトの動作になりますが、一部の条件では、応答を正しく処理するために内部に追加のif-elseが必要です。条件の維持が困難になり、if-else条件が多数ある巨大なメソッドを使用するのは良くないようです。

私はそれらを処理するための最良の方法になると思います。コードは次のようになります。

public Result ProcessStockState(StockResponse response)
{
   if(response.state==StockStateEnum.Ok)
   {
       if(response.Sender==Sender1){ /* code....*/ }
       else if ( response.Sender==Sender2){/*code ...*/ }
       else {/**default handling code...*/}
   }
   else if(response.state==StockStateEnum.Unkown)
   {
       if(response.Sender==Sender5){ /* code....*/ }
       else if ( response.Sender==Sender7){/*code ...*/ }
       else if ( response.Sender==Sender10){/*code ...*/ }
       else {/*default handling code ...*/}
   }
   else if(response.state==X)
   {
       /*Multiple conditions that ussually differ from the other ifs*/
       /*It always have a default behaviour*/
   } 
}

カスケード型のif-elseを回避するためにどのデザイン/パターンを推奨しますか?

4
X.Otano

If/elseチェーンの問題は、それらが柔軟性に欠け、そのクラスが使用されている実装と緊密に結合していることです。

動的なアプローチについては、私が this answer で述べたのと同じソリューションをお勧めします。

基本的に、一連の応答ハンドラー応答マネージャーを作成します。最後は適切なハンドラーへの応答のディスパッチを担当します。

ソリューションはpolymorphismに依存しています。すべてのハンドラーは、2つのメソッドを持つインターフェース(IStockResponseHandler)を実装する必要があります。

  • bool CanHandle(StockResponse response)
  • Result Handle(StockResponse response)

次に、StockResponseManagerインスタンスのリストを使用してIStockResponseHandlerを初期化します。

最後に、応答を受け取ったら、正しい結果を得るためにStockResponseManager.getResult(response)を呼び出すだけです。

私のリンクされた回答でも述べたように、利点は将来を見据えています。プロジェクトで追加の作業をほとんどまたはまったく行うことなく、if/elseチェーンにさらに静的なif条件を追加することなく、新しいタイプの応答を処理できます。ルールをデータベースからロードした場合、理論的にはプログラムに変更を加えることなく動的に応答を処理できます。

原則として、IStockResponseHandlerタイプごとに1つのResultクラスを作成します。つまり、2つの条件が同じResult値を返す可能性があり、この場合、2つの個別のクラスではなく、同じクラスで処理する必要があります。あなたの例では、可能な各response.state値のクラスになると思います。

8
Neil

特に複雑なパターンはありません。トップレベルの選択肢をProcessOKResponse()ProcessUnknownResponse()などの個別のメソッドに分割し、ディスパッチロジックのみをエントリポイントメソッドに残すだけです。

後で大きなswitchを変換できます。ルックアップテーブル、または継承によるダイナミックディスパッチ、または他の何かに、しかし重要なことは1つのコードブロックで行われる決定の量を減らすことです。より小さなメソッドにリファクタリングすることがその鍵です。

(メソッドルックアップテーブルの疑似コードの例:

handlers = [
   OK: handle_ok, 
   unknown: handle_unknown, 
   backorder: handle_backorder
]

def handle_ok():
   if(response.Sender==Sender1){ /* code....*/ }
   else if ( response.Sender==Sender2){/*code ...*/ }
   else {/**default handling code...*/}

def handle_unknown():
   ...

def handle_response(response):
  handlers[response.state]()

動的ディスパッチのコード例:

class OKResponse:
    def dispatch():
        if condition1:
            action1()
        Elif condition2:
            action2()
        else:
            action0()

class UnknownResponse:
    def dispatch():
        ...

def handle_response(response_object):
    response_object.dispatch()
6
Kilian Foth

MainメソッドがResultを返すことを考えると、else ifステートメントをif + returnだけで簡略化できます。

そして、他の多くの人が明らかに示唆したように、それはifステートメント本体をメソッドに抽出することには意味があります。

私はそれがこのようなものになると思います:

public Result ProcessStockState(StockResponse response) {
   if(response.state==StockStateEnum.Ok) {
       return handleResponseOk();
   }

   if(response.state==StockStateEnum.Unkown) {
       return handleResponseUnknown();
   }

   if(response.state==X) {
       return handleResponseX();
   }

   return defaultResult; 
}

handleResponseメソッドで使用できる同じアプローチ。

2
xagaffar

関数を分離するために第2レベルのifを因数分解することを考えてください。たとえば、handleResponseOK()およびhandleResponseUnknown()です。その後、コードは次のようになります。

public Result ProcessStockState(StockResponse response)
{
    if(response.state==StockStateEnum.Ok)
    {
        handleResponseOK(response);
    }
    else if(response.state==StockStateEnum.Unkown)
    {
        handleResponseUnknown(response);
    }
    else  if(response.state==X)
    {
        handleResponseX(response);
    }
}

決定を下さなければならない場合でも、それを行うためのロジックを配置する必要がありますどこかそして、もし複数必要な場合は...まあ、それをエスケープすることはできません。

確かに、コードをおしゃれなデザインパターンのブランドの服で着ることができますが、本当にそうする必要がありますか?インデントを減らす必要があるだけで、ifを隠すために複雑な構造をコーディングしたくない場合は、古き良きリファクタリングで十分でしょう。

2
Mael

Java列挙型疑似コードを使用する:

public enum StockStateEnum {

    OK(
        public void process(Response response) {
          if(response.Sender==Sender1){ /* code....*/ }
          else if ( response.Sender==Sender2){/*code ...*/ }
          else {/**default handling code...*/}
        }
    ),

    UNKOWN(
      public void process(Response response) { 
        if(response.Sender==Sender5){ /* code....*/ }
        else if ( response.Sender==Sender7){/*code ...*/ }
        else if ( response.Sender==Sender10){/*code ...*/ }
        else {/*default handling code ...*/}
      }
    ),
    ...

    public abstract process(Response response);

    public static StockStateEnum findByState(Response response) {
      // Loop through enum values trying to find a match
      for (StockStateEnum stockState : StockStateEnum.values() {
        if (stockState == response.state) {
          return stockState;
        }
      }
      return StockStateEnum.DEFAULT;  // Or null, depending on requirement
    }
}

使用法:

public Result ProcessStockState(StockResponse response) {
  StockStateEnum stockState = StockStateEnum.findByState(response);
  stockState.process(response);
  ...
}

私の推測では、response.Senderは、ifステートメントの数と循環的複雑度をさらに削減します。

1
ootero

ポリモーフィズムは、必要なものを達成するのに役立つテクニックです。また、コードを分離し、将来の拡張性を高めるのに役立ちます。以下は、if-else条件を解除する手法の実装です。

すべてのStockResponseHandlerを使用してディクショナリを作成します。つまり、Ok、unknownなどです。

        StockEnum stockEnum = response.state;
        Dictionary<StockEnum, IStockResponseHandler> dictionary = new Dictionary<StockEnum, IStockResponseHandler>();
        dictionary.Add(StockEnum.Ok,new StockResponseOkHandler());
        dictionary.Add(StockEnum.Unknown, new StockResponseUnknownHandler());
        dictionary.Add(StockEnum.X, new StockResponderXHandler());

        if(dictionary.ContainsKey(stockEnum))
        {
            IStockResponseHandler stockResponseHandler = null;
            dictionary.TryGetValue(stockEnum,out stockResponseHandler);
            stockResponseHandler.Validate(response.Sender);
        }

注:最後のステートメントは、ISenderをそれぞれのハンドラーに送信します

OKハンドラーと他のハンドラーは次のようになります。

public class StockResponseOkHandler : IStockResponseHandler
{
    public StockResponseOkHandler()
    {
    }

    public void Validate(ISender sender)
    {
        sender.WriteSenderSpecificCode();
    }
}

あなたは彼らの中に各送信者のための別々のコードを書くことができます、それは送信者のためのものです1

using System;
namespace ChainReactionWithInterface
{
    public class Sender1Handler : ISender
    {
        public Sender1Handler()
        {
        }

        public void WriteSenderSpecificCode()
        {
            Console.WriteLine("Called 1");
        }
    }
}

したがって、すべての要素を分離し、いつでも、新しい条件を追加でき、スケーラブルになります。この手法で私が目にする唯一の欠点は、クラスの初期化ですが、おそらく、遅延読み込みまたは何らかの処理によってそれを最適化する他の方法があるかもしれません。

1
vin