Cette question est motivée par quelque chose que j'ai récemment commencé à voir un peu trop souvent, le if..else if..else
structure. Bien que ce soit simple et qu'il ait ses utilisations, quelque chose à ce sujet ne cesse de me raconter à nouveau et encore que cela puisse être substitué à quelque chose de plus fin, élégant et plus facile de rester à jour.
Pour être aussi précis que possible, c'est ce que je veux dire:
if (i == 1) {
doOne();
} else if (i == 2) {
doTwo();
} else if (i == 3) {
doThree();
} else {
doNone();
}
Je peux penser à deux façons simples de réécrire cela, soit par ternaire (ce qui n'est qu'une autre façon d'écrire la même structure):
(i == 1) ? doOne() :
(i == 2) ? doTwo() :
(i == 3) ? doThree() : doNone();
ou à l'aide de la carte (en Java et je pense que dans C # aussi) ou dictionnaire ou une autre structure K/V comme ceci:
public interface IFunctor() {
void call();
}
public class OneFunctor implemets IFunctor() {
void call() {
ref.doOne();
}
}
/* etc. */
Map<Integer, IFunctor> methods = new HashMap<Integer, IFunctor>();
methods.put(1, new OneFunctor());
methods.put(2, new TwoFunctor());
methods.put(3, new ThreeFunctor());
/* .. */
(methods.get(i) != null) ? methods.get(i).call() : doNone();
En fait, la méthode de la carte ci-dessus est ce que j'ai fini par faire la dernière fois, mais maintenant, je ne peux pas m'empêcher de penser qu'il doit y avoir de meilleures alternatives en général pour ce problème exact.
Donc, que les autres-et les meilleurs moyens de remplacer le si..el oilent si..ele sont là-bas et lequel est votre préféré?
D'accord, voici vos pensées :
Premièrement, la réponse la plus populaire était la déclaration de commutation, comme si:
switch (i) {
case 1: doOne(); break;
case 2: doTwo(); break;
case 3: doThree(); break;
default: doNone(); break;
}
Cela ne fonctionne que pour les valeurs qui peuvent être utilisées dans des commutateurs, qui au moins dans Java sont tout à fait limiter un facteur. Acceptable pour des cas simples cependant, naturellement.
L'autre et peut-être une manière un peu plus féroce que vous semblez suggérer est de le faire en utilisant le polymorphisme. La conférence YouTube liée par CMS est une excellente horlogerie, allez-y ici: "The Clean Code Talks - Héritage, Polymorphisme et test" aussi loin que j'ai compris, cela se traduirait par quelque chose comme ça :
public interface Doer {
void do();
}
public class OneDoer implements Doer {
public void do() {
doOne();
}
}
/* etc. */
/* some method of dependency injection like Factory: */
public class DoerFactory() {
public static Doer getDoer(int i) {
switch (i) {
case 1: return new OneDoer();
case 2: return new TwoDoer();
case 3: return new ThreeDoer();
default: return new NoneDoer();
}
}
}
/* in actual code */
Doer operation = DoerFactory.getDoer(i);
operation.do();
Deux points intéressants de Google Talk:
Ainsi, en outre, la CDR qui mérite d'être mentionnée à mon avis, la CDR a fourni ses habitudes pervers avec nous et sans recommandation à utiliser, il est tout simplement très intéressant de regarder.
Merci à tous pour les réponses (jusqu'à présent), je pense que j'ai peut-être appris quelque chose aujourd'hui!
Ces constructions peuvent souvent être remplacées par le polymorphisme. Cela vous donnera un code plus court et moins fragile.
Dans les langues orientées objet, il est courant d'utiliser le polymorphisme pour remplacer les ifs.
J'ai aimé cette conversation sur Google Clean Code qui couvre le sujet:
The Clean Code Talks - Héritage, polymorphisme et test
ABSTRAIT
Votre code est-il plein de déclarations si? Déclarations de commutation? Avez-vous la même déclaration de commutation dans divers endroits? Lorsque vous apportez des modifications, vous trouvez-vous la même modification à la même chose si/commutateur à plusieurs endroits? Avez-vous déjà oublié un?
Cette discussion discutera des approches de l'utilisation de techniques orientées objet pour éliminer de nombreux conditionnels. Le résultat est un code plus propre, plus serré et mieux conçu, plus facile à tester, à comprendre et à entretenir.
Un relevé de commutateur:
switch(i)
{
case 1:
doOne();
break;
case 2:
doTwo();
break;
case 3:
doThree();
break;
default:
doNone();
break;
}
Il y a deux parties à cette question.
Comment à expédier en fonction d'une valeur? Utilisez une instruction de commutation. Il affiche votre intention la plus clairement.
quand Pour expédier en fonction d'une valeur? Seulement à un endroit par valeur: Créez un objet polymorphe qui sait fournir le comportement attendu de la valeur.
En fonction du type de chose que vous êtes si..lez, envisagez de créer une hiérarchie d'objets et d'utiliser le polymorphisme. Ainsi:
class iBase
{
virtual void Foo() = 0;
};
class SpecialCase1 : public iBase
{
void Foo () {do your magic here}
};
class SpecialCase2 : public iBase
{
void Foo () {do other magic here}
};
Ensuite, dans votre code, appelez P-> FOO () et la bonne chose va arriver.
Utilisez un commutateur/boîtier qu'il est propre: p
Le commutateur instruction bien sûr, beaucoup plus jolie, alors tous ceux-ci etc.
Dans ce cas simple, vous pouvez utiliser un commutateur.
Sinon, une approche basée sur une table a l'air bien, ce serait mon deuxième choix chaque fois que les conditions sont suffisamment régulières pour le rendre applicable, en particulier lorsque le nombre de cas est important.
Le polymorphisme serait une option s'il n'y a pas trop de cas, et les conditions et le comportement sont irréguliers.
En dehors de l'utilisation d'une déclaration de commutation, qui peut être plus rapide, aucune. Si sinon est clair et facile à lire. avoir à regarder les choses dans une carte obscurcir les choses. Pourquoi rendre le code plus difficile à lire?
switch (i) {
case 1: doOne(); break;
case 2: doTwo(); break;
case 3: doThree(); break;
default: doNone(); break;
}
Après avoir tapé cela, je dois dire qu'il n'y a pas beaucoup de mal avec votre déclaration IF. Comme Einstein a dit: "Rendez-le aussi simple que possible, mais pas plus simple".
J'utilise la main courte suivante juste pour le plaisir! N'essayez aucune autre que si le Code Clearity vous concerne plus que le nombre de caractères dactylographiés.
Pour les cas où DOX () revient toujours vrai.
i==1 && doOne() || i==2 && doTwo() || i==3 && doThree()
Bien sûr, j'essaie de vous assurer que la plupart des fonctions vides renvoient 1 simplement pour vous assurer que ces courtes mains sont possibles.
Vous pouvez également fournir des missions.
i==1 && (ret=1) || i==2 && (ret=2) || i==3 && (ret=3)
Comme instad d'écriture
if(a==2 && b==3 && c==4){
doSomething();
else{
doOtherThings();
}
Écrivez
a==2 && b==3 && c==4 && doSomething() || doOtherThings();
Et dans les cas où vous n'êtes pas sûr de ce que la fonction reviendra, ajoutez un || 1 :-)
a==2 && b==3 && c==4 && (doSomething()||1) || doOtherThings();
Je trouve toujours plus vite à taper que d'utiliser tous ceux-ci si-d'autre et cela effraie certainement tous les nouveaux noobs. Imaginez une page complète de déclaration comme celle-ci avec 5 niveaux d'indentation.
"Si" est rare dans certains de mes codes et je l'ai donné le nom "SI-MOINS PROGRAMMATION" :-)
L'exemple donné dans la question est assez trivial pour fonctionner avec un simple commutateur. Le problème vient lorsque les Si-elsdes sont imbriqués plus profondément et plus profonds. Ils ne sont plus "clairs ni faciles à lire" (comme quelqu'un d'autre argument) et l'ajout de nouveau code ou de réparer des bugs devient de plus en plus difficile et plus difficile d'être sûr parce que vous pourriez ne pas vous retrouver si la logique est complexe.
J'ai vu cela arriver beaucoup de fois (commutateurs 4 niveaux profonds et des centaines de lignes longues - impossibles à maintenir), en particulier à l'intérieur des classes d'usine qui essaient de faire trop pour trop de types différents non liés.
Si les valeurs que vous comparez contre ne sont pas des entiers sans signification, mais une sorte d'identifiant unique (c'est-à-dire en utilisant Enums comme polymorphisme de l'homme pauvre), vous souhaitez utiliser des classes pour résoudre le problème. S'ils ne sont vraiment que des valeurs numériques, je préférerais utiliser des fonctions distinctes pour remplacer le contenu des blocs IF et d'autre, et ne concevez pas une sorte de hiérarchie de classe artificielle pour les représenter. En fin de compte, cela peut entraîner un code Messier que les spaghettis d'origine.
switch
Déclaration ou classes avec des fonctions virtuelles comme solution de fantaisie. Ou une éventail de pointeurs à fonctions. Tout dépend de la manière dont les conditions complexes sont, parfois, il n'y a aucun moyen de ceux-ci. Et encore une fois, la création de séries de classes pour éviter une instruction de commutation est clairement fausse, le code doit être aussi simple que possible (mais pas plus simple)
J'irais jusqu'à dire qu'aucun programme ne devrait jamais utiliser d'autre. Si vous vous demandez des ennuis. Vous ne devriez jamais supposer si ce n'est pas un X, il doit être un Y. Vos tests doivent tester chaque individuellement et échouer à la suite de ces tests.
In OO Paradigm, vous pouvez le faire en utilisant un bon ancien ancien polymorphisme. Trop grand si - les structures ou les constructions de commutation sont parfois considérées comme une odeur dans le code.
La méthode de la carte est à peu près du mieux qu'il y a. Cela vous permet d'encapsuler les déclarations et de briser les choses assez bien. Le polymorphisme peut le compléter, mais ses objectifs sont légèrement différents. Il introduit également des arbres de classe inutiles.
Les commutateurs ont l'inconvénient des relevés de pause manquants et tombent à travers et encouragent vraiment ne pas rompre le problème en pièces plus petites.
Cela étant dit: Un petit arbre d'if..else est bien (en fait, j'ai fait valoir en faveur des jours à propos de 3 if..els au lieu d'utiliser la carte récemment). C'est lorsque vous commencez à mettre une logique plus compliquée dans laquelle cela devient un problème due à la maintenabilité et à la lisibilité.
En Python, j'écrirais votre code comme suit:
actions = {
1: doOne,
2: doTwo,
3: doThree,
}
actions[i]()
Je considère ces if-elseif -... construit comme "bruit de mots clés". Bien que cela puisse être clair ce qu'il fait, il manque de concision; Je considère la concision comme une partie importante de la lisibilité. La plupart des langues offrent quelque chose comme une déclaration switch
. Construire une carte est un moyen d'obtenir quelque chose de similaire dans des langues qui n'ont pas de tel, mais cela ressemble certainement à une solution de contournement, et il y a un peu de surcharge (une instruction de commutation se traduit par des opérations de comparaison simples et des sauts conditionnels, mais une carte Tout d'abord est construit en mémoire, puis interrogé et seulement le comparateur et le saut ont lieu).
Dans la LISP commune, il existe deux constructions de commutation intégrées, cond
et case
. cond
permet des conditionnels arbitraires, tandis que case
uniquement des tests d'égalité, mais sont plus concis.
(Cond ((= I 1) [.____] (DO-One)) [.____] (((= i 2) [.____] (do-deux)) ] ((= i 3) (DO-trois)) [.____] (T [.____] (ne) (.____]] [.____]
(cas i [.____] (1 (do-one)) [.____] (2 (DO-deux)) [.____] (3 (DO-trois)) (sinon.] ne pas))))
Bien sûr, vous pouvez créer votre propre case
- comme macro pour vos besoins.
Dans Perl, vous pouvez utiliser l'instruction for
, éventuellement avec une étiquette arbitraire (ici: SWITCH
):
SWITCH: for ($i) {
/1/ && do { do_one; last SWITCH; };
/2/ && do { do_two; last SWITCH; };
/3/ && do { do_three; last SWITCH; };
do_none; };
Utilisez un opérateur ternaire!
Opérateur ternaire (53Characters):
i===1?doOne():i===2?doTwo():i===3?doThree():doNone();
Si (108Characters):
if (i === 1) {
doOne();
} else if (i === 2) {
doTwo();
} else if (i === 3) {
doThree();
} else {
doNone();
}
Interrupteur ((même plus longtemps que si!?!?) 114Characters):
switch (i) {
case 1: doOne(); break;
case 2: doTwo(); break;
case 3: doThree(); break;
default: doNone(); break;
}
c'est tout ce dont vous avez besoin! Ce n'est qu'une ligne et c'est assez soigné, de façon plus courte que le commutateur et si!
Naturellement, cette question dépend de la langue, mais une déclaration de commutation pourrait être une meilleure option dans de nombreux cas. Un bon compilateur C ou C++ pourra générer une Table de saut , ce qui sera nettement plus rapide pour les grands ensembles de cas.
Si vous devez vraiment avoir un tas de tests et que vous voulez faire des choses différentes quand un test est vrai, je recommanderais une boucle tandis que si seulement ifs- Néant. Chacun si un test a-t-il un appelle une méthode puis éclate de la boucle. Personne, il n'y a rien de pire qu'un tas d'empilés si/else/si/sinon, etc.