Multiple If-else ou enum - lequel est préférable et pourquoi?
Voici le code original:
public class FruitGrower {
public void growAFruit(String type) {
if ("wtrmln".equals(type)) {
//do watermelon growing stuff
} else if ("ppl".equals(type)) {
//do apple growing stuff
} else if ("pnppl".equals(type)) {
//do pineapple growing stuff
} else if ("rng".equals(type)) {
//do orange growing stuff
} else {
//do other fruit growing stuff
}
}
}
C'est la façon dont je l'ai changé:
public class FruitGrower {
enum Fruits {
WATERMELON {
@Override
void growAFruit() {
//do watermelon growing stuff
}
},
APPLE {
@Override
void growAFruit() {
//do apple growing stuff
}
},
PINEAPPLE {
@Override
void growAFruit() {
//do pineapple growing stuff
}
},
ORANGE {
@Override
void growAFruit() {
//do orange growing stuff
}
},
OTHER {
@Override
void growAFruit() {
//do other fruit growing stuff
}
};
static void grow(String type) {
if ("wtrmln".equals(type)) {
WATERMELON.growAFruit();
} else if ("ppl".equals(type)) {
APPLE.growAFruit();
} else if ("pnppl".equals(type)) {
PINEAPPLE.growAFruit();
} else if ("rng".equals(type)) {
ORANGE.growAFruit();
} else {
OTHER.growAFruit();
}
};
abstract void growAFruit();
}
public void growAFruit(String type) {
Fruits.grow(type);
}
}
Je vois que enums
code est plus long et peut-être pas aussi claire que if-else
code, mais je crois que c'est mieux, quelqu'un pourrait-il me dire, pourquoi je suis mal (ou peut-être que je ne suis pas)?
UPD - modifié le code source pour plus de problème spécifique. Je vais reformuler la question: il y a des préoccupations au sujet de l'aide enum au lieu de if-else?
source d'informationauteur dhblah
Vous devez vous connecter pour publier un commentaire.
Vous avez déjà de bonnes réponses sur l'amélioration de votre utilisation des Énumérations. Quant à savoir pourquoi ils sont mieux que les constantes de chaîne:
Je pense que le plus grand avantage est au moment de la compilation de vérification d'erreur. Si je devais faire appel
growAFruit("watermelon")
le compilateur n'a aucune idée de chose qui n'allait pas. Et depuis que je l'ai orthographié correctement, il ne va pas se démarquer comme une erreur lorsque vous affichez le code. Mais si je devaisWATERMELEN.growAFruit()
le compilateur peut vous dire tout de suite que j'ai mal orthographié.Vous obtenez également de définir
growAFruit
comme un tas de simple, facile à lire, méthodes, au lieu d'un gros bloc deif
-then
-else
s. Cela devient encore plus évident lorsque vous avez quelques dizaines de fruits, ou lorsque vous commencez à ajouterharvestAFruit()
packageAFruit()
sellAFruit()
etc. Sans les énumérations, vous seriez copie de votre grand if-else bloc de tous les cours, et si vous avez oublié d'ajouter un cas, il tombe à travers les cas de défaut ou de ne rien faire, alors qu'avec les énumérations, le compilateur peut vous dire que la méthode n'a pas été mis en œuvre.Encore plus au compilateur de vérifier la bonté: Si vous aussi vous avez un
growVegetable
méthode et les constantes de chaîne, il n'y a rien qui vous empêche d'appelergrowVegetable("pineapple")
ougrowFruit("peas")
. Si vous avez un"tomato"
constante, la seule façon de savoir si vous considérez que c'est un fruit ou un légume est de lire le code source de la pertinence des méthodes. Une fois de plus, avec les énumérations, le compilateur peut vous le dire tout de suite si vous avez fait quelque chose de mal.Un autre avantage est qu'il groupes de constantes liées ensemble, et leur donne une bonne maison. L'autre solution étant un tas de
public static final
champs jeté dans quelque classe que ce qui arrive à les utiliser, ou collé une interface de constantes. L'interface complète des constantes n'a même pas de sens parce que si vous n'avez pas besoin de définir l'enum est beaucoup plus facile que l'écriture de l'interface. Aussi, dans des classes ou des interfaces, il ya la possibilité d'utiliser accidentellement la même valeur pour plus d'une constante.Ils sont également itérable. Pour obtenir toutes les valeurs de l'enum, vous pouvez simplement appeler
Fruit.values()
alors qu'avec des constantes que vous devez créer et de remplir votre propre tableau. Ou si simplement à l'aide de littéraux comme dans votre exemple, il n'y a aucune autorité liste des valeurs valides.Le Tour de Bonus: IDE Soutien
La raison principale pas d'utiliser un enum serait si vous ne connaissez pas toutes les valeurs possibles au moment de la compilation (c'est à dire vous avez besoin d'ajouter plus de valeur à ce que le programme est en cours d'exécution). Dans ce cas, vous souhaiterez peut-être définir comme une classe de la hiérarchie. Aussi, ne pas jeter un tas de sans rapport avec les constantes dans un enum et l'appeler un jour. Il devrait y avoir une sorte de fil conducteur qui relie les valeurs. Vous pouvez toujours faire plusieurs énumérations si c'est plus approprié.
Les énumérations sont le chemin à parcourir, mais vous pouvez améliorer considérablement votre code comme ceci:
Oh, vous avez besoin d'un cas de défaut, ce qui le rend un peu plus difficile. Bien sûr, vous pouvez faire ceci:
Mais c'est assez laid. Je crois que je l'avais à quelque chose comme ceci:
Aussi, si tous vos enum méthodes de faire est de retourner une valeur, vous devez refactoriser votre conception de sorte que vous initialiser les valeurs d'un constructeur et de les renvoyer dans une méthode définie dans la classe Enum, pas les éléments individuels:
Vous avez seulement fait la moitié des changements pour être plus propre. La croissance de la méthode doit être modifié comme ceci:
Et
Fruits
devrait être renomméFruit
: une pomme est un fruit, pas un de fruits.Si vous avez vraiment besoin de garder votre chaîne de types, puis de définir une méthode (dans l'énumération de la classe elle-même, par exemple) pour retourner le Fruit associé à chaque type. Mais la plupart du code, il faut utiliser des Fruits au lieu de String.
Je pense que vous voulez un
Map<String, Fruit>
(ou<String, FruitGrower>
).Cette carte peut être rempli automatiquement par le enum constructeurs, ou par un initialiseur statique. (Il pourrait même carte plusieurs noms sur la même constante enum, si certains fruits ont des noms d'alias.)
Votre
grow
méthode ressemble alors à ceci:De cours, avez-vous vraiment besoin de la chaîne ici? Ne devriez-vous pas utiliser toujours les enum objet?
Je ne suis pas certain que j'allais utiliser les Énumérations ici. J'ai peut-être manqué quelque chose (?), mais je pense que ma solution devrait ressembler à quelque chose comme ceci, avec des classes séparées pour chaque type de fruit, le tout basé sur l'un des genres les fruits de la classe:
Je pense que vous êtes sur la bonne voie. J'irais et de le jeter dans quelques octets supplémentaires pour une table de hachage pour se débarrasser de la chaîne de commutation de bloc. Cela vous donne à la fois, plus propre apparence, moins de code et probablement un peu plus de performance.
Et c'est la façon dont vous l'utiliser:
Simple, pas besoin d'un FruitGrower, mais allez-y si vous pensez que c'est nécessaire.
J'deuxième Sean Patrick Floyd sur que les énumérations sont le chemin à parcourir, mais voudrais ajouter que vous pouvez raccourcir votre code d'événement de plus en plus de façon spectaculaire à l'aide d'un système comme ceci:
Aussi, la "croissance" de la méthode est douteuse. Ne devrait-il pas être quelque chose comme
Vous pouvez aussi l'améliorer en utilisant une variable pour stocker le gimmeFruit valeur et inititialize avec le constructeur.
(Je n'ai pas fait de compiler cette donc il peut y avoir quelques erreurs de syntaxe)
EDIT:
Si le type de la méthode cultivons, ce n'est pas la même chaîne de caractères, puis utiliser une Carte pour définir les matches de type Enum et le retour de la recherche à partir de la carte.
J'ai souvent de mettre en œuvre une méthode dans les énumérations de l'analyse d'une Chaîne de caractères et renvoie le correspondant constante enum. J'ai toujours le nom de cette méthode
parse(String)
.Parfois, je la surcharge de cette méthode pour analyser les enum constante par l'autre, compte tenu du type d'entrée, trop.
C'est la mise en œuvre est toujours le même:
Parcourir toutes les valeurs de l'enum() et revenir quand vous le frappez. Enfin faire un retour comme fallthrough - souvent spécifique constante enum ou
null
. Dans la plupart des cas, je préfère null.Si vous n'avez pas vraiment besoin de ce
Fruit.OTHER
de le supprimer. Ou comment un "Autre-fruits" pousse? oORetour
null
puis dansparse(String)
méthode fallthrough une valeur null à vérifier avant d'appelergrow()
dansgrowAFruit(String)
.C'est une bonne idée d'ajouter
@CheckForNull
annotation à l'parse(String)
méthode, puis.L'API publique est exactement le même. Vous avez toujours le même if-else bloc, c'est juste dans l'enum maintenant. Donc, je pense que c'est pas mieux. Si quoi que ce soit, c'est pire, à cause de la complexité.
Que signifie "faire de la culture des fruits trucs"? Parlez-vous des trucs cultivateur n' (jusqu'à ce que le sol, planter des graines, etc), ou des trucs du fruit lui-même n'en fait germer, germer, fleurir, etc)? Dans l'exemple original, les actions sont définies par la
FruitGrower
mais dans vos modifications qu'ils sont définis par laFruit
. Cela fait une grande différence lorsque vous envisagez de sous-classement. Par exemple, je souhaiterez peut-être définir unMasterFruitGrower
qui utilise différents procédés pour cultiver des fruits mieux. Avoir lagrow()
opération définie dansFruit
rend plus difficile de raisonner sur.Comment complexe sont le fruit de plus en plus? Si vous êtes préoccupé par la longueur de la ligne de l'if-else bloc, je pense que c'est une bonne idée de définir séparé pour les fruits des méthodes de culture (
growWatermelon()
growOrange()
...) ou de définir unFruitGrowingProcedure
de l'interface, la mise en œuvre de sous-classes pour chaque type de fruit, et de les stocker dans une carte ou fixé en vertu de l'FruitGrower
.La réponse à votre question est d'utiliser les énumérations, ou mieux encore, les usines et le polymorphisme, comme mentionné ci-dessus. Toutefois, si vous voulez vous débarrasser de commutateurs (qui est ce que votre if-else est vraiment en train de faire) complètement , un bon moyen, si pas le meilleur, afin de le faire est d'utiliser l'inversion de contrôle. Donc, je vous suggère d'utiliser le printemps, comme suit:
Maintenant, créez le fruit de localisateur d'interface, comme suit:
Laisser la classe principale de disposer d'une référence à un FruitLocator objet, un setter pour elle, et il suffit d'appeler le getFruit commande:
Maintenant vient la partie difficile. Définir votre FruitGrower classe comme un printemps de haricots, ainsi que votre FruitLocator et des Fruits:
Seule chose qui reste à faire est de créer un fruits.les propriétés de fichier dans votre classpath et ajouter le type de grain de cartographie, comme suit:
Maintenant, vous pouvez ajouter des fruits autant que vous le souhaitez, vous avez seulement besoin de créer un nouveau fruit de la classe, de la définir comme un haricot et ajouter un mappage de votre fichier de propriétés. Beaucoup beaucoup plus évolutif que la chasse pour if-else logique dans le code.
Je sais que cela peut sembler complexe au premier abord, mais je pense que le sujet serait incomplète sans parler de l'Inversion de Contrôle.
Pour répondre à votre question, je dirais ni l'if-else ou les énumérations sont préférables à votre problème spécifique. Une bonne façon d'aborder ce problème serait d'utiliser l'encapsulation et d'abstraction et de laisser l'héritage de gérer le "type" pour vous.
La tâche de cultiver des fruits varie grandement entre chaque fruit. Il serait judicieux, pour faire de chaque fruit de sa propre classe permettant une plus grande flexibilité, plus tard, lorsqu'plus de fonctionnalités peuvent être nécessaires.
Heres un bon exemple:
Une fois que le produit des classes ont été définies, nous pouvons créer une classe qui se développe de fruits sans aucune "identifier" vérification de type.
Bien, les enums code est sûrement plus. Ce que je suggère:
Si différents Fruits ont différents croître comportements, je ne voudrais pas utiliser un if-else ou un enum, et au lieu d'utiliser une conception orientée objet. Le problème avec le if-else/enum style (je envisager de leur équivalent dans votre exemple) est qu'ils collectent des comportements des différents types d'objets en un seul endroit. Si vous ajoutez une nouvelle de fruits, vous devez modifier votre if-else/enum à chaque fois. Cela viole le ouvert-fermé principe.
Prendre en compte si vos 4 fruits a 3 comportements (par exemple, de grandir, de mûrir, de la pourriture). Vous avez un if-else/enum pour chaque comportement, chaque contenant 4 de fruits références. Considérons maintenant l'ajout d'un 5ème de fruits, vous devez modifier 3 if-else/enum blocs. Le comportement d'un melon d'eau n'a rien à voir avec une pomme, mais ils sont dans le même morceau de code. Considérons maintenant l'ajout d'un nouveau fruit de comportement (par exemple, arôme) - vous devez créer un nouveau if-else/enum, qui a les mêmes problèmes.
Je crois que la bonne solution dans la plupart des cas est l'utilisation de classes (par exemple, un Fruit/l'interface de la classe de base avec une application de classe par fruit) et de mettre le comportement de chaque classe au lieu de rassembler en un seul endroit. Ainsi, lorsque vous ajoutez un nouveau fruit, le seul code à changé, c'est que une nouvelle de fruits classe est écrite.
Il est bien connu refactoring vous pouvez utiliser pour prendre votre code existant et de migrer vers quoi je parle. Il est appelé Remplacer le Conditionnel avec le Polymorphisme.