Considérez l'exemple ci-dessous. Toute modification de l'énumération ColorChoice affecte toutes les sous-classes IWindowColor.
Les énumérations ont-elles tendance à provoquer des interfaces fragiles? Existe-t-il quelque chose de mieux qu'une énumération pour permettre une plus grande flexibilité polymorphe?
enum class ColorChoice
{
Blue = 0,
Red = 1
};
class IWindowColor
{
public:
ColorChoice getColor() const=0;
void setColor( const ColorChoice value )=0;
};
Edit: désolé d'utiliser la couleur comme exemple, ce n'est pas de cela qu'il s'agit. Voici un exemple différent qui évite le hareng rouge et fournit plus d'informations sur ce que j'entends par flexibilité.
enum class CharacterType
{
orc = 0,
elf = 1
};
class ISomethingThatNeedsToKnowWhatTypeOfCharacter
{
public:
CharacterType getCharacterType() const;
void setCharacterType( const CharacterType value );
};
De plus, imaginez que les poignées de la sous-classe ISomethingThatNeedsToKnowWhatTypeOfCharacter appropriée soient distribuées par un modèle de conception d'usine. J'ai maintenant une API qui ne peut pas être étendue à l'avenir pour une autre application où les types de caractères autorisés sont {humain, nain}.
Edit: Juste pour être plus concret sur ce sur quoi je travaille. Je conçois une liaison forte de cette spécification ( MusicXML ) et j'utilise des classes enum pour représenter les types de la spécification qui sont déclarés avec xs: enumeration. J'essaie de penser à ce qui se passera lorsque la prochaine version (4.0) sortira. Ma bibliothèque de classes peut-elle fonctionner en mode 3.0 et en mode 4.0? Si la prochaine version est 100% rétrocompatible, alors peut-être. Mais si les valeurs d'énumération ont été supprimées de la spécification, je suis mort dans l'eau.
la source
Réponses:
Lorsqu'ils sont utilisés correctement, les énumérations sont beaucoup plus lisibles et robustes que les «nombres magiques» qu'ils remplacent. Je ne les vois pas normalement rendre le code plus cassant. Par exemple:
value
s'agit d'une valeur de couleur valide ou non. Le compilateur l'a déjà fait.enum class
fonctionnalité du C ++ moderne vous permet même de forcer les gens à toujours écrire le premier au lieu du second.cependant, utilisation d'une énumération pour la couleur est discutable car dans de nombreuses situations (la plupart?), Il n'y a aucune raison de limiter l'utilisateur à un si petit ensemble de couleurs; vous pourriez tout aussi bien les laisser passer des valeurs RVB arbitraires. Sur les projets avec lesquels je travaille, une petite liste de couleurs comme celle-ci n'apparaîtrait jamais que dans le cadre d'un ensemble de «thèmes» ou de «styles» censés agir comme une fine abstraction sur les couleurs du béton.
Je ne suis pas sûr de savoir ce que signifie votre question sur la «flexibilité polymorphe». Les énumérations n'ont pas de code exécutable, il n'y a donc rien à rendre polymorphe. Vous recherchez peut-être le modèle de commande ?
Edit: Après l'édition, je ne sais toujours pas quel type d'extensibilité vous recherchez, mais je pense toujours que le modèle de commande est la chose la plus proche que vous obtiendrez à une "énumération polymorphe".
la source
Non, non. Il y a deux cas: les exécutants seront soit
stocker, renvoyer et transmettre des valeurs d'énumération sans jamais les utiliser, auquel cas elles ne sont pas affectées par les modifications de l'énumération, ou
opérer sur des valeurs d'énumération individuelles, auquel cas tout changement dans l'énumération doit bien sûr, naturellement, inévitablement, nécessairement , être pris en compte avec un changement correspondant dans la logique du réalisateur.
Si vous mettez "Sphère", "Rectangle" et "Pyramide" dans une énumération "Forme", et passez une telle énumération à une
drawSolid()
fonction que vous avez écrite pour dessiner le solide correspondant, puis un matin, vous décidez d'ajouter un " Ikositetrahedron "à l'énumération, vous ne pouvez pas vous attendredrawSolid()
fonction reste inchangée; Si vous vous attendiez à ce qu'il dessine en quelque sorte des icositetrahedrons sans que vous ayez d'abord à écrire le code réel pour dessiner des icositetrahedrons, c'est votre faute, pas la faute de l'énumération. Donc:Non, ils ne le font pas. Ce qui cause des interfaces fragiles, c'est que les programmeurs se considèrent comme des ninjas, essayant de compiler leur code sans activer suffisamment d'avertissements. Ensuite, le compilateur ne les avertit pas que leur
drawSolid()
fonction contient uneswitch
instruction à laquelle il manque unecase
clause pour la valeur d'énumération "Ikositetrahedron" nouvellement ajoutée.La façon dont il est censé fonctionner est analogue à l'ajout d'une nouvelle méthode virtuelle pure à une classe de base: vous devez ensuite implémenter cette méthode sur chaque héritier, sinon le projet ne construit pas et ne devrait pas construire.
Maintenant, à vrai dire, les énumérations ne sont pas une construction orientée objet. Ils sont davantage un compromis pragmatique entre le paradigme orienté objet et le paradigme de programmation structurée.
La façon pure et simple de faire les objets n'est pas du tout d'avoir des énumérations, mais plutôt d'avoir des objets tout le long.
Donc, la manière purement orientée objet d'implémenter l'exemple avec les solides est bien sûr d'utiliser le polymorphisme: au lieu d'avoir une seule méthode centralisée monstrueuse qui sait tout dessiner et d'avoir à dire quel solide dessiner, vous déclarez un " Classe "solide" avec une
draw()
méthode abstraite (virtuelle pure) , puis vous ajoutez les sous-classes "Sphère", "Rectangle" et "Pyramide", chacune ayant sa propre implémentationdraw()
qui sait se dessiner.De cette façon, lorsque vous introduisez la sous-classe "Ikositetrahedron", vous n'aurez qu'à lui fournir une
draw()
fonction, et le compilateur vous rappellera de le faire, en ne vous laissant pas instancier "Icositetrahedron" autrement.la source
default:
clause le fassent. Une recherche rapide sur le sujet n'a pas donné de résultats plus précis, donc cela pourrait convenir comme sujet d'une autreprogrammers SE
question.Pyramid
fait savoir comment fairedraw()
une pyramide. Au mieux, il peut dériverSolid
et avoir uneGetTriangles()
méthode et vous pouvez la transmettre à unSolidDrawer
service. Je pensais que nous nous éloignions des exemples d'objets physiques en tant qu'exemples d'objets dans la POO.Les énumérations ne créent pas d'interfaces fragiles. Abuser des énumérations le fait.
À quoi servent les énumérations?
Les énumérations sont conçues pour être utilisées comme des ensembles de constantes nommées de manière significative. Ils doivent être utilisés lorsque:
Bonnes utilisations des énumérations:
System.DayOfWeek
) À moins que vous n'ayez affaire à un calendrier incroyablement obscur, il n'y aura jamais que 7 jours de la semaine.System.ConsoleColor
) Certains peuvent être en désaccord avec cela, mais .Net a choisi de le faire pour une raison. Dans le système de console .Net, il n'y a que 16 couleurs disponibles pour une utilisation par la console. Ces 16 couleurs correspondent à la palette de couleurs héritées connue sous le nom de CGA ou «Color Graphics Adapter» . Aucune nouvelle valeur ne sera jamais ajoutée, ce qui signifie qu'il s'agit en fait d'une application raisonnable d'une énumération.Thread.State
) Les concepteurs de Java ont décidé que dans le modèle de thread Java, il n'y aura jamais qu'un ensemble fixe d'états dans lesquels aThread
peut être, et donc pour simplifier les choses, ces différents états sont représentés comme des énumérations . Cela signifie que de nombreuses vérifications basées sur l'état sont de simples if et commutateurs qui, en pratique, fonctionnent sur des valeurs entières, sans que le programmeur n'ait à se soucier de ce que sont réellement les valeurs.System.Text.RegularExpressions.RegexOptions
) Les Bitflags sont une utilisation très courante des énumérations. Si commun en fait, que dans .Net, toutes les énumérations ont uneHasFlag(Enum flag)
méthode intégrée. Elles prennent également en charge les opérateurs au niveau du bit et il y a unFlagsAttribute
pour marquer une énumération comme étant destinée à être utilisée comme un ensemble d'indicateurs de bit. En utilisant une énumération comme un ensemble d'indicateurs, vous pouvez représenter un groupe de valeurs booléennes dans une seule valeur, ainsi que les indicateurs clairement nommés pour plus de commodité. Cela serait extrêmement bénéfique pour représenter les drapeaux d'un registre d'état dans un émulateur ou pour représenter les autorisations d'un fichier (lecture, écriture, exécution), ou à peu près n'importe quelle situation où un ensemble d'options connexes ne s'excluent pas mutuellement.Mauvaises utilisations des énumérations:
True
drapeau impliqueFalse
). Ce comportement peut être davantage pris en charge grâce à l'utilisation de fonctions spécialisées (ieIsTrue(flags)
etIsFalse(flags)
).la source
RegexOptions
Msdn.microsoft.com/en-us/library/…Les énumérations sont une grande amélioration par rapport aux numéros d'identification magiques pour les ensembles fermés de valeurs qui n'ont pas beaucoup de fonctionnalités associées. Habituellement, vous ne vous souciez pas du nombre réellement associé à l'énumération; dans ce cas, il est facile d'étendre en ajoutant de nouvelles entrées à la fin, aucune fragilité ne devrait en résulter.
Le problème est lorsque vous avez des fonctionnalités importantes associées à l'énumération. Autrement dit, vous avez ce genre de code qui traîne:
Un ou deux d'entre eux est correct, mais dès que vous avez ce genre d'énumération significative, ces
switch
déclarations ont tendance à se multiplier comme des tribus. Maintenant, chaque fois que vous étendez l'énumération, vous devez rechercher tous lesswitch
es associés pour vous assurer que vous avez tout couvert. C'est fragile. Des sources telles que Clean Code suggèrent que vous devriez en avoir unswitch
par énumération, tout au plus.Dans ce cas, vous devez plutôt utiliser les principes OO et créer une interface pour ce type. Vous pouvez toujours conserver l'énumération pour communiquer ce type, mais dès que vous devez en faire quoi que ce soit, vous créez un objet associé à l'énumération, éventuellement à l'aide d'une fabrique. C'est beaucoup plus facile à maintenir, car il vous suffit de chercher un seul endroit à mettre à jour: votre usine, et en ajoutant une nouvelle classe.
la source
Si vous faites attention à la façon dont vous les utilisez, je ne considérerais pas les énumérations comme nuisibles. Mais il y a quelques points à considérer si vous souhaitez les utiliser dans du code de bibliothèque, par opposition à une seule application.
Ne jamais supprimer ou réorganiser les valeurs. Si vous avez à un moment donné une valeur enum dans votre liste, cette valeur doit être associée à ce nom pour toute l'éternité. Si vous le souhaitez, vous pouvez à un moment donné renommer des valeurs en
deprecated_orc
ou autre chose, mais en ne les supprimant pas, vous pouvez plus facilement maintenir la compatibilité avec l'ancien code compilé avec l'ancien ensemble d'énumérations. Si un nouveau code ne peut pas traiter les anciennes constantes d'énumération, générez-y une erreur appropriée ou assurez-vous qu'aucune valeur n'atteigne ce morceau de code.Ne faites pas d'arithmétique sur eux. En particulier, ne faites pas de comparaisons d'ordres. Pourquoi? Parce qu'alors, vous pourriez rencontrer une situation où vous ne pouvez pas conserver les valeurs existantes et conserver un ordre sain en même temps. Prenez par exemple une énumération pour les directions de la boussole: N = 0, NE = 1, E = 2, SE = 3,… Maintenant, si après une mise à jour, vous incluez NNE et ainsi de suite entre les directions existantes, vous pouvez soit les ajouter à la fin de la liste et ainsi casser l'ordre, ou vous les entrelacer avec les clés existantes et ainsi casser les mappages établis utilisés dans le code hérité. Ou vous dépréciez toutes les anciennes clés et vous avez un tout nouveau jeu de clés, ainsi qu'un code de compatibilité qui se traduit entre les anciennes et les nouvelles clés pour le bien du code hérité.
Choisissez une taille suffisante. Par défaut, le compilateur utilise le plus petit entier pouvant contenir toutes les valeurs d'énumération. Ce qui signifie que si, lors d'une mise à jour, votre ensemble d'énumérations possibles s'étend de 254 à 259, vous avez soudainement besoin de 2 octets pour chaque valeur d'énumération au lieu d'un. Cela pourrait casser la structure et les dispositions de classe partout, essayez donc d'éviter cela en utilisant une taille suffisante dans la première conception. C ++ 11 vous donne beaucoup de contrôle ici, mais sinon, spécifier une entrée
LAST_SPECIES_VALUE=65535
devrait également aider.Avoir un registre central. Étant donné que vous souhaitez corriger le mappage entre les noms et les valeurs, il est mauvais si vous laissez des utilisateurs tiers de votre code ajouter de nouvelles constantes. Aucun projet utilisant votre code ne doit être autorisé à modifier cet en-tête pour ajouter de nouveaux mappages. Au lieu de cela, ils devraient vous embêter pour les ajouter. Cela signifie que pour l'exemple humain et nain que vous avez mentionné, les énumérations sont en effet mal adaptées. Là, il serait préférable d'avoir une sorte de registre au moment de l'exécution, où les utilisateurs de votre code peuvent insérer une chaîne et récupérer un numéro unique, bien emballé dans un type opaque. Le «nombre» pourrait même être un pointeur vers la chaîne en question, cela n'a pas d'importance.
Malgré le fait que mon dernier point ci-dessus rend les énumérations mal adaptées à votre situation hypothétique, votre situation réelle où certaines spécifications pourraient changer et vous devrez mettre à jour du code semble correspondre assez bien avec un registre central pour votre bibliothèque. Les énumérations devraient donc être appropriées si vous prenez à cœur mes autres suggestions.
la source