web-dev-qa-db-fra.com

éviter les déclarations si

Je pensais à la conception orientée objet aujourd'hui, et je me demandais si vous devriez éviter les déclarations. Mon idée est que, dans tous les cas où vous avez besoin d'une instruction if, vous pouvez simplement créer deux objets qui implémentent la même méthode. Les deux implémentations de méthodes seraient simplement les deux branches possibles de l'instruction if si.

Je me rends compte que cela semble extrême, mais il semble que vous puissiez essayer de le discuter dans une certaine mesure. Des idées à ce sujet?

MODIFIER

Waouh ça n'a pas pris longtemps. Je suppose que c'est beaucoup trop extrême. Est-il possible de dire cependant que, sous OOP, vous devriez vous attendre à beaucoup moins de déclarations? 

SECOND EDIT

Qu'en est-il: un objet qui détermine l'implémentation de sa méthode en fonction de ses attributs. Cela signifie que vous pouvez implémenter someMethod() de deux manières et spécifier certaines restrictions. A tout moment, un objet sera dirigé vers la bonne implémentation de méthode en fonction de ses propriétés. Donc, dans le cas de if(x > 5), il suffit de disposer de deux méthodes qui reposent sur l'attribut x

35
Ori

Je peux vous dire une chose. Quoi que l'on dise, penser à simplifier et à éliminer les ramifications inutiles est un signe de votre maturité en tant que développeur de logiciels. Il y a de nombreuses raisons pour lesquelles la création de branches est mauvaise: tests, maintenance, taux de bogues plus élevé, etc. C’est l’une des choses que je recherche lors des entretiens avec des personnes et un excellent indicateur de leur maturité en tant que développeur. Je vous encourage à continuer à expérimenter, à simplifier votre code et votre conception en utilisant moins de conditions. Quand j'ai fait ce changement, j'ai trouvé beaucoup moins de temps à déboguer mon code, cela fonctionnait simplement, puis quand je devais changer quelque chose, les changements étaient super faciles à faire car la plupart du code était séquentiel. Encore une fois, je vous encouragerais à 100% à continuer de faire ce que vous faites, peu importe ce que les autres disent. Gardez à l'esprit que la plupart des développeurs travaillent et pensent à un niveau beaucoup plus bas et ne font que suivre les règles. Alors bon travail à ce sujet.

35
vance

Expliquez comment implémenter les éléments suivants sans instruction if ou logique ternaire:

if ( x < 5 ) {
   x = 0
} else {
   print x;
}
12
Stefan Kendall

Oui, il est vrai que les conditionnelles complexes peuvent souvent être simplifiées avec polymorphishm. Mais ce n'est pas utile tout le temps. Allez lire le livre de Fowler sur le refactoring pour avoir une idée du moment.

http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html

9
Frank Schwieterman

Éliminer complètement si les déclarations n’est pas réaliste et je ne pense pas que c’est ce que Ori suggère. Mais ils peuvent souvent être remplacés par le polymorphisme. (Et ainsi peut changer beaucoup de déclarations).

Francesco Cirillo a lancé la campagne Anti-If pour sensibiliser à ce problème. Il dit:

Savoir utiliser des objets permet aux développeurs d'éliminer les IF basés sur le type, ceux qui compromettent le plus souvent la flexibilité et la capacité d'évolution du logiciel.

Vous ou votre équipe pouvez également rejoindre la campagne .

8
Dawie Strauss

Un de mes professeurs avait l'habitude de dire ça. J'ai tendance à penser que les gens qui sont si dogmatiques sur ce genre de choses ne programment généralement pas leur vie.

6
e-satis

Éviter la déclaration: Il y a plusieurs façons de faire, l'une d'entre elles est la suivante:

int i=0;
if(i==1)
{
//Statement1
}

if(i==2)
{
//Statement2
}

if(i==3)
{
//Statement3
}

if(i==4)
{
//Statement4
}

Utilisation du dictionnaire et du délégué:

delegate void GetStatement ();

Dictionary<int,GetStatement > valuesDic=new Dictionary<int,GetStatement >();

void GetStatement1()
{
//Statement1
}
void GetStatement2()
{
//Statement2
}
void GetStatement3()
{
//Statement3
}


void GetStatement4()
{
//Statement4
}

void LoadValues()
{
valuesDic.Add(1,GetStatement1);
valuesDic.Add(2,GetStatement2);
valuesDic.Add(3,GetStatement3);
valuesDic.Add(4,GetStatement4);

}

Remplacer la déclaration si:

int i=0;
valuesDic[i].Invoke();
5
SreeKanth

Jetez un oeil sur la campagne anti-if L'idée n'est pas de remplacer tous les si dans votre application par la stratégie ou le modèle d'état. L'idée est que lorsque vous avez une logique de branchement complexe, basée notamment sur une énumération, vous devriez envisager de refactoriser le modèle de stratégie.

Et ce cas, vous pouvez supprimer le si tous ensemble en utilisant une usine. Voici un exemple relativement simple. Bien sûr, comme je l'ai dit dans un cas réel, la logique de vos stratégies serait un peu plus complexe que de simplement afficher "Je suis actif".

public enum WorkflowState
{
  Ready,
  Active,
  Complete
}

public interface IWorkflowStrategy
{
  void Execute();
}

public class ActiveWorkflowStrategy:IWorkflowStrategy
{
  public void Execute()
  {
    Console.WriteLine("The Workflow is Active");
  }
}

public class ReadyWorkflowStrategy:IWorkflowStrategy
{
  public void Execute()
  {
    Console.WriteLine("The Workflow is Ready");
  }
}

public class CompleteWorkflowStrategy:IWorkflowStrategy
{
  public void Execute()
  {
    Console.WriteLine("The Workflow is Complete");
  }
}

public class WorkflowStrategyFactory
{
  private static Dictionary<WorkflowState, IWorkflowStrategy> _Strategies= 
    new Dictionary<WorkflowState, IWorkflowStrategy>();
  public WorkflowStrategyFactory()
  {
    _Strategies[WorkflowState.Ready]=new ReadyWorkflowStrategy();
    _Strategies[WorkflowState.Active]= new ActiveWorkflowStrategy();
    _Strategies[WorkflowState.Complete = new CompleteWorkflowStrategy();
  }
  public IWorkflowStrategy GetStrategy(WorkflowState state)
  {
    return _Strategies[state];
  }
}

public class Workflow
{
    public Workflow(WorkflowState state)
    {
        CurrentState = state;
    }
    public WorkflowState CurrentState { get; set; }
}

public class WorkflowEngine
{
    static void Main(string[] args)
    {
        var factory = new WorkflowStrategyFactory();
        var workflows =
            new List<Workflow>
                {
                    new Workflow(WorkflowState.Active),
                    new Workflow(WorkflowState.Complete),
                    new Workflow(WorkflowState.Ready)
                };
        foreach (var workflow in workflows)
        {
            factory.GetStrategy(workflow.CurrentState).
                Execute();
        }
    }
}   
5
Michael Brown

À certains égards, cela peut être une bonne idée. Swiching sur un champ de type à l'intérieur d'un objet est généralement une mauvaise idée lorsque vous pouvez utiliser des fonctions virtuelles à la place. Mais le mécanisme de la fonction virtuelle n'est en aucun cas destiné à remplacer le test if () en général.

4
anon

Cela dépend de ce que la déclaration originale compare. Ma règle générale est que s'il s'agit d'une switch ou if testant l'égalité par rapport à une énumération, c'est un bon candidat pour une méthode séparée. Toutefois, les instructions switch et if sont utilisées pour de nombreux autres types de tests - il n’existe aucun moyen efficace de remplacer les opérateurs relationnels (<, >, <=, >=) par des méthodes spécialisées, et certains types de tests énumérés fonctionnent beaucoup mieux avec déclarations standard.

Donc, vous ne devriez remplacer ifs que s'ils ressemblent à ceci:

if (obj.Name == "foo" || obj.Name == "bar") { obj.DoSomething(); }
else if (obj.Name == "baz") { obj.DoSomethingElse(); }
else { obj.DoDefault(); }
3
John Millikin

En réponse à la question de ifTrue:

Eh bien, si vous avez des classes ouvertes et un système de types dépendant suffisamment fort, c'est facile, même si c'est un peu idiot. De manière informelle et dans aucune langue particulière:

class Nat {
    def cond = {
        print this;
        return this;
    }
}

class NatLessThan<5:Nat> { // subclass of Nat
    override cond = {
        return 0;
    }
}

x = x.cond();

(a continué...)

Ou, sans classes ouvertes mais en supposant plusieurs classes d'envoi et anonymes:

class MyCondFunctor {
    function branch(Nat n) {
        print n;
        return n;
    }

    function branch(n:NatLessThan<5:Nat>) {
        return 0;
    }
}

x = new MyCondFunctor.branch(x);

Ou, comme avant mais avec des classes anonymes:

x = new {
    function branch(Nat n) {
        print n;
        return n;
    }

    function branch(n:NatLessThan<5:Nat>) {
        return 0;
    }
}.branch(x);

Vous auriez beaucoup plus de facilité si vous revoyiez cette logique, bien sûr. N'oubliez pas qu'il existe des systèmes de type Turing-complet complets.

3
Thom Smith

Comment décidez-vous de la méthode de l'objet à utiliser sans instruction if?

3
iBiryukov
Assume we have conditional values.

public void testMe(int i){

if(i=1){
somevalue=value1;
}


if(i=2){
 somevalue=value2;
}

if(i=3){
somevalue=value3;
}

}

//**$$$$$you can replace the boring IF blocks with Map.$$$$$**

// =========================================== ============

Same method would look like this:
--------------------------------
public void testMe(int i){

Map<Integer,String> map = new HashMap<Integer,String>();

map.put(1,value1);
map.put(2,value2);
map.put(3,value3);

}
This will avoid the complicated if conditions.

Vous pouvez utiliser une solution similaire lorsque vous utilisez des modèles d'usine pour charger des classes.

public void loadAnimalsKingdom(String animalKingdomType)
    if(animalKingdomType="bird"){
    Bird b = new Bird();
    }
    if(animalKingdomType="animal"){
    Animal a= new Animal();
    }
    if(animalKingdomType="reptile"){
    Reptile r= new Reptile();
    }
  }

Maintenant, en utilisant la carte:

   public void loadAnimalsKingdom(String animalKingdomType)
    {
       Map <String,String> map = new HashMap<String,String>();

       map.put("bird","com.animalworld.Bird.Class");
       map.put("animal","com.animalworld.Animal.Class");
       map.put("reptile","com.animalworld.Reptile.Class");

       map.get(animalKingdomType);

***Use class loader to load the classes on demand once you extract the required class from the map.***
}

Vous aimez la solution? Donne le pouce levé. - Vv

2

La création d'une toute nouvelle classe pour une else, bien que techniquement réalisable, aboutirait probablement à un code difficile à lire, à maintenir, voire à prouver correct.

2
fbrereto

C'est une idée intéressante. Je pense que vous pourriez théoriquement le faire, mais ce serait une douleur énorme dans un langage qui n’est pas spécialement conçu pour le supporter. Je ne vois certainement aucune raison de le faire.

1
Thom Smith

Je pense que ce qu'il dit ou ce qu'il veut dire, c'est qu'il pense qu'il est préférable d'éviter de trop abuser du "marquage" et de l'ajout de fonctionnalités personnalisées à une classe par plusieurs déclarations if s'il est plus logique de sous-classer ou de repenser l'objet. hiérarchie.

1
GreenieMeanie

Je pense que l'application de cet argument à l'idée de chaque déclaration if est assez extrême, mais certaines langues vous permettent d'appliquer cette idée dans certains scénarios.

Voici un exemple d'implémentation Python que j'ai écrit dans le passé pour un deque de taille fixe (file d'attente à deux extrémités). Au lieu de créer une méthode "remove" et d'avoir des instructions if pour voir si la liste est pleine ou non, vous créez simplement deux méthodes et les réaffectez à la fonction "remove" selon vos besoins.

L'exemple suivant répertorie uniquement la méthode "remove", mais il existe évidemment des méthodes "append" et similaires.

class StaticDeque(collections.deque):

    def __init__(self, maxSize):

        collections.deque.__init__(self)
        self._maxSize = int(maxSize)
        self._setNotFull()

    def _setFull(self):

        self._full = True
        self.remove = self._full_remove

    def _setNotFull(self):

        self._full = False
        self.remove = self._not_full_remove

    def _not_full_remove(self,value):

        collections.deque.remove(self,value)

    def _full_remove(self,value):

        collections.deque.remove(self,value)
        if len(self) != self._maxSize and self._full:
            self._setNotFull()

Dans la plupart des cas, ce n'est pas une idée utile, mais parfois cela peut être utile.

0
Brent Writes Code

Je conviens avec Vance que le SI n’est pas bon, car il augmente la complexité conditionnelle et doit être évité autant que possible.

Le polymorphisme est une solution totalement viable à condition d'être utilisé pour donner un sens et non pour "éviter si".

Une note latérale qui ne correspond pas à vos exigences OOP, mais l'approche orientée données tend également à évite la création de branches .

0
FloFu

Je dirai que la réponse est vaguement oui-ish. Particulièrement lorsque le langage permet une programmation fonctionnelle lourde (par exemple, C #, F #, OCaml).

Un composant qui contient 2 si if couple fortement deux règles métier, alors rompez-le.

Prenez cela comme une règle très générale, mais je suis d’accord. Si vous avez plusieurs déclarations if, vous devriez peut-être envisager une autre approche.

0
George Mauer

Les instructions if sont assez essentielles à la programmation, aussi vous ne pouvez pas les éviter.

Cependant, un objectif clé de la programmation orientée objet (en fait, l’un des «piliers») est l’encapsulation. L'ancienne règle "encapsuler ce qui varie" vous aide à supprimer les instructions problématiques if et case où vous essayez de prendre en compte toutes les options de votre objet. Une meilleure solution pour traiter des branches, des cas spéciaux, etc. consiste à utiliser quelque chose comme le motif de conception "Factory" (Abstract Factory ou Factory Method - selon les besoins, bien sûr).

Par exemple, plutôt que de laisser votre boucle de code principale vérifier le SE de vos instructions if avec des instructions if, puis créer une fenêtre pour créer des fenêtres GUI avec des options différentes pour chaque SE, votre code principal créerait un objet de la fabrique, qui utilisera le SE pour déterminer quel SE - objet concret spécifique à réaliser. Ce faisant, vous extrayez les variations (et les clauses longues if-then-else) de votre boucle de code principale et laissez les objets enfants le gérer - la prochaine fois que vous devrez apporter une modification telle que la prise en charge d'un nouveau système d'exploitation , vous ajoutez simplement une nouvelle classe à partir de l'interface d'usine.

0
ewall

J'ai suivi le discours anti-si récemment et cela me semble être une rhétorique extrême/hyperbolique. Cependant, je pense qu’il ya vérité dans cette déclaration: souvent la logique d’une déclaration if peut être mise en œuvre de manière plus appropriée via le polymorphisme. Je pense qu’il est bon de garder cela à l’esprit chaque fois que vous faites une déclaration if. Cela étant dit, j'estime que la déclaration if est toujours une structure logique fondamentale et qu'il ne faut pas la craindre ni l'éviter en tant que principe.

0
Daniel Auger

Vous devez comprendre ce que signifie réellement (x > 5). En supposant que x représente un nombre, il "classe" fondamentalement tous les nombres supérieurs à cinq. Donc, le code ressemblerait à ceci dans un langage avec une syntaxe python:

class Number(Object):

    # ... Number implementation code ... #

    def doSomething():
        self = 0
        return self

    def doSomethingElse():
        pass

class GreaterThan5(Number):
    def doSomething():
        print "I am " + self

    def doSomethingElse():
        print "I like turtles!"

Ensuite, nous pourrions exécuter le code suivant:

>>> type(3)
<class Number>
>>> type(3+3)
<class GreaterThan5>
>>> 3.doSomething()
0
>>> (3 + 3).doSomething()
I am 6
>>> (7 - 3).doSomethingElse()
>>>

La conversion de type automatique ici est importante. Autant que je sache, aucune des langues actuelles ne vous permet de gâcher autant avec des entiers.

En fin de compte, vous pouvez faire dans votre code, peu importe. Tant que les personnes qui lisent cela peuvent comprendre immédiatement. Donc, la dépêche polymorphe sur les entiers ou tout ce qui n'est pas ordinaire doit avoir un très bon raisonnement derrière.

0
t0suj4

Mes deux brins de ce que je comprends de l’approche orientée objet -

Premièrement, quels objets dans un programme doivent être intuitifs. C'est-à-dire que je ne devrais pas essayer de créer une classe 'Arithmatique' pour fournir des fonctions mathématiques. Ceci est un abus de OOD.

Deuxièmement, c’est une très forte opinion de moi. Il ne doit pas s'appeler conception orientée objet mais conception orientée objet et ! Si les noms de méthode des objets ne sont pas eux-mêmes intuitifs, les objets hérités risquent alors de réimplémenter les méthodes déjà disponibles.

L'approche orientée objet, selon moi, ne remplace pas l'approche procédurale. C’est plutôt principalement pour deux raisons principales pour les créateurs de la langue -

  1. Meilleure capacité de définition des variables. 

  2. Meilleure capacité de récupération de place plutôt que d'avoir trop de variables globales.

0
abhijit

C'est assez extrême. Faire ce que vous suggérez causerait beaucoup de duplication de code inutile, à moins que la fonction entière soit complètement différente, basée sur un seul environnement if; et si oui, cela aurait probablement dû être de l'autre côté de l'invocation de la méthode.

Les déclarations if ont certainement leur place dans la conception orientée objet.

0
Williham Totland

Il faut sûrement faire une comparaison, peu importe ce que vous faites. En fin de compte ... vous pouvez certainement éviter les instructions if, mais vous produiriez du code IDENTIQUE au code à l'aide d'une instruction if.

Quelqu'un me corrige si je me trompe, mais je ne peux pas penser à un moment où vous pourriez obtenir une forme gagnante en faisant cela.

0
Goz