web-dev-qa-db-fra.com

Meilleure approche - convertir plusieurs conditionnels si -else dans un design plus pratique

J'ai une classe qui gère l'état d'une réponse, appelée StockResponse. Le code a plusieurs ifs pour gérer chaque état du stock. La plupart des cas ont un comportement par défaut, mais certaines conditions ont besoin d'un supplément si, d'autre à l'intérieur pour manipuler correctement la réponse. Les conditions deviennent difficiles à maintenir et sont mauvaises pour avoir une énorme méthode avec beaucoup de conditions if-else.

Je suppose que ce sera le meilleur moyen de les gérer. Le code ressemble maintenant à ceci:

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*/
   } 
}

Quel conception/motif que vous me recommanderiez d'éviter cela en cascade si-d'autre?

4
X.Otano

Le problème avec des chaînes if/sinon est qu'ils sont inflexibles et qui couplent étroitement cette classe avec toutes les implémentations utilisées le cas échéant.

Pour une approche dynamique, je recommanderais la même solution que j'ai mentionnée dans cette réponse .

Fondamentalement, vous créez un ensemble de gestionnaires de réponse et d'un gestionnaire de réponse . Le dernier est chargé de l'envoi de la réponse au gestionnaire approprié .

La solution repose sur le polymorphisme . Tous les gestionnaires doivent mettre en œuvre une interface (IStockResponseHandler) avec deux méthodes:

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

Ensuite, vous initialisez le StockResponseManager avec une liste des instances IStockResponseHandler.

Enfin, une fois que vous avez une réponse, vous n'avez besoin que d'appeler la fonction StockResponseManager.getResult(response) afin d'obtenir le résultat approprié.

Comme je l'ai également mentionné dans ma réponse liée, les avantages sont à la recherche future. Vous vous permettez de gérer de nouveaux types de réponses avec peu de travaux supplémentaires dans votre projet et sans ajouter un autre statique si l'état de votre chaîne if/autre. Si vous avez chargé des règles d'une base de données, vous pouvez en théorie traiter de manière dynamique les réponses sans apporter de modifications dans votre programme.

En règle générale, je créerais une classe IStockResponseHandler par Result Type. En d'autres termes, deux conditions peuvent entraîner la même valeur Result, et dans ce cas, elle devrait être traitée par la même classe, et non deux classes distinctes. Dans votre exemple, je pense que ce serait une classe pour chaque réponse possible.state valeur.

8
Neil

Il n'y a pas de modèle particulièrement compliqué que j'utiliserais. Il suffit de rompre les alternatives de niveau supérieur dans des méthodes individuelles telles que ProcessOKResponse(), ProcessUnknownResponse(), etc. et laissez seule la logique d'expédition dans la méthode du point d'entrée.

Vous pouvez ensuite transformer le grand switch, par ex. dans une table de recherche ou dans une envoi dynamique par héritage, ou autre chose, mais l'important est de réduire la quantité de décisions prises dans un bloc de code. Le refactoring à des méthodes plus petites est la clé de cela.

(Exemple de pseudo code pour une méthode Table de recherche:

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]()

Exemple de code pour Dynamic Dispatch:

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

Considérant que la méthode principale est le résultat de retour, vous pouvez simplifier sinon instructions avec simplement si + retour.

Et autant d'autres gars suggèrent évidemment, il est logique d'extraire si instructions de déclaration dans une méthode.

Je suppose que cela pourrait sembler quelque chose comme ça:

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; 
}

la même approche que vous pouvez prendre Handleresponse Méthodes.

2
xagaffar

Pensez à affirmer le deuxième niveau IFS pour séparer les fonctions. Par exemple handleResponseOK() et handleResponseUnknown(). Ensuite, votre code ressemblera à ceci:

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);
    }
}

Lorsque vous devez prendre une décision, vous devez toujours mettre la logique pour le faire quelque part et si vous avez besoin de plus d'un si ... Eh bien, il n'y a pas d'échapper cela.

Bien sûr, vous pouvez habiller votre code dans des vêtements de marque de conception de fantaisie, mais avez-vous vraiment besoin de? Peut-être juste un bon vieux refactoring suffira si vous avez juste besoin de réduire l'empreinte et de ne pas vouloir coder les constructions élaborées pour cacher votre IFS.

2
Mael

Utilisation Java Enum pseudocode:

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
    }
}

Usage:

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

Je suppose que vous pourriez faire quelque chose de similaire avec la réponse.Sversaire réduisant encore plus le nombre de déclarations et de complexité cyclomatique.

1
ootero

Le polymorphisme est une technique qui peut vous aider à réaliser ce dont vous avez besoin. Cela vous aide à découpler le code et rendez votre code plus évolutif pour l'avenir. Voici ma mise en œuvre de la technique qui brise la condition if-else.

Créez un dictionnaire avec tous les StockResponseHandlers I.e OK, Inconnu, etc.

        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);
        }

Remarque: la dernière déclaration envoie le isender au gestionnaire respectif

Ok gestionnaire et autres gestionnaires ressembleraient à ceci comme suit:

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

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

Vous pouvez écrire un code séparé pour chaque expéditeur dans leur, ci-dessous, c'est pour l'expéditeur 1

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

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

Nous avons donc découplé tous les éléments et à tout moment, nous pouvons ajouter de nouvelles conditions et elle sera évolutive. Le seul inconvénient que je vois dans cette technique est l'initialisation des classes. Il y a peut-être d'autres optimiser pour le bien par le chargement paresseux ou une chose en quelque sorte.

1
vin