TL; DR Faites défiler jusqu'en bas.
D'après ce que je vois, vous implémentez un nouveau langage en plus de C #. Les énumérations semblent indiquer le type d'un identifiant (ou tout ce qui a un nom et qui apparaît dans le code source de la nouvelle langue), qui semble être appliqué aux nœuds qui doivent être ajoutés dans une arborescence du programme.
Dans cette situation particulière, il existe très peu de comportements polymorphes entre les différents types de nœuds. En d'autres termes, bien qu'il soit nécessaire que l'arbre puisse contenir des nœuds de types (variantes) très différents, la visite réelle de ces nœuds aura essentiellement recours à une chaîne géante si-alors-sinon (ou instanceof
/ is
vérifie). Ces contrôles géants se produiront probablement dans de nombreux endroits différents du projet. C'est la raison pour laquelle les énumérations peuvent sembler utiles, ou elles sont au moins aussi utiles que instanceof
/ is
checks.
Le modèle de visiteur pourrait toujours être utile. En d'autres termes, il existe différents styles de codage qui peuvent être utilisés à la place de la chaîne géante de instanceof
. Cependant, si vous voulez une discussion sur les divers avantages et inconvénients, vous auriez choisi de présenter un exemple de code de la chaîne la plus laide instanceof
du projet, au lieu de chicaner sur les énumérations.
Cela ne veut pas dire que les classes et la hiérarchie d'héritage ne sont pas utiles. Plutôt l'inverse. Bien qu'il n'y ait pas de comportements polymorphes qui fonctionnent dans tous les types de déclarations (à part le fait que chaque déclaration doit avoir une Name
propriété), il existe de nombreux comportements polymorphes riches partagés par les frères et sœurs proches. Par exemple, Function
et Procedure
partagent probablement certains comportements (les deux étant appelables et acceptant une liste d'arguments d'entrée saisis), et PropertyGet
hériteront certainement des comportements de Function
(les deux ayant un ReturnType
). Vous pouvez utiliser des énumérations ou des vérifications d'héritage pour la chaîne géante if-then-else, mais les comportements polymorphes, même fragmentés, doivent toujours être implémentés dans les classes.
Il sont beaucoup des conseils en ligne contre l' utilisation excessive de instanceof
/ is
contrôles. La performance n'est pas une des raisons. La raison est plutôt d'empêcher le programmeur de découvrir organiquement des comportements polymorphes appropriés, comme si instanceof
/ is
est une béquille. Mais dans votre situation, vous n'avez pas d'autre choix, car ces nœuds ont très peu en commun.
Voici maintenant quelques suggestions concrètes.
Il existe plusieurs façons de représenter les groupements non foliaires.
Comparez l'extrait suivant de votre code d'origine ...
[Flags]
public enum DeclarationType
{
Member = 1 << 7,
Procedure = 1 << 8 | Member,
Function = 1 << 9 | Member,
Property = 1 << 10 | Member,
PropertyGet = 1 << 11 | Property | Function,
PropertyLet = 1 << 12 | Property | Procedure,
PropertySet = 1 << 13 | Property | Procedure,
LibraryFunction = 1 << 23 | Function,
LibraryProcedure = 1 << 24 | Procedure,
}
à cette version modifiée:
[Flags]
public enum DeclarationType
{
Nothing = 0, // to facilitate bit testing
// Let's assume Member is not a concrete thing,
// which means it doesn't need its own bit
/* Member = 1 << 7, */
// Procedure and Function are concrete things; meanwhile
// they can still have sub-types.
Procedure = 1 << 8,
Function = 1 << 9,
Property = 1 << 10,
PropertyGet = 1 << 11,
PropertyLet = 1 << 12,
PropertySet = 1 << 13,
LibraryFunction = 1 << 23,
LibraryProcedure = 1 << 24,
// new
Procedures = Procedure | PropertyLet | PropertySet | LibraryProcedure,
Functions = Function | PropertyGet | LibraryFunction,
Properties = PropertyGet | PropertyLet | PropertySet,
Members = Procedures | Functions | Properties,
LibraryMembers = LibraryFunction | LibraryProcedure
}
Cette version modifiée évite d'allouer des bits à des types de déclarations non concrets. Au lieu de cela, les types de déclaration non concrets (regroupements abstraits de types de déclaration) ont simplement des valeurs d'énumération qui sont le bit ou (l'union des bits) entre tous ses enfants.
Il y a une mise en garde: s'il existe un type de déclaration abstraite qui a un seul enfant, et s'il est nécessaire de faire la distinction entre l'abstrait (parent) et le concret (enfant), l'abstrait aura toujours besoin de son propre bit .
Une mise en garde spécifique à cette question: a Property
est initialement un identifiant (quand vous voyez juste son nom, sans voir comment il est utilisé dans le code), mais il peut se transformer en PropertyGet
/ PropertyLet
/ PropertySet
dès que vous voyez comment il est utilisé dans le code. En d'autres termes, à différentes étapes de l'analyse, vous devrez peut-être marquer un Property
identifiant comme étant "ce nom fait référence à une propriété", puis le remplacer par "cette ligne de code accède à cette propriété d'une certaine manière".
Pour résoudre cette mise en garde, vous pourriez avoir besoin de deux ensembles d'énumérations; une énumération indique ce qu'est un nom (identifiant); une autre énumération indique ce que le code essaie de faire (par exemple, déclarer le corps de quelque chose; essayer d'utiliser quelque chose d'une certaine manière).
Déterminez si les informations auxiliaires sur chaque valeur d'énumération peuvent être lues à la place à partir d'un tableau.
Cette suggestion s'exclut mutuellement avec d'autres suggestions, car elle nécessite de reconvertir les puissances de deux en petites valeurs entières non négatives.
public enum DeclarationType
{
Procedure = 8,
Function = 9,
Property = 10,
PropertyGet = 11,
PropertyLet = 12,
PropertySet = 13,
LibraryFunction = 23,
LibraryProcedure = 24,
}
static readonly bool[] DeclarationTypeIsMember = new bool[32]
{
?, ?, ?, ?, ?, ?, ?, ?, // bit[0] ... bit[7]
true, true, true, true, true, true, ?, ?, // bit[8] ... bit[15]
?, ?, ?, ?, ?, ?, ?, true, // bit[16] ... bit[23]
true, ... // bit[24] ...
}
static bool IsMember(DeclarationType dt)
{
int intValue = (int)dt;
return (intValue < 0 || intValue >= 32) ? false : DeclarationTypeIsMember[intValue];
// you can also throw an exception if the enum is outside range.
}
// likewise for IsFunction(dt), IsProcedure(dt), IsProperty(dt), ...
La maintenabilité va être problématique.
Vérifiez si un mappage un à un entre les types C # (classes dans une hiérarchie d'héritage) et vos valeurs d'énumération.
(Vous pouvez également modifier vos valeurs d'énumération pour garantir un mappage un à un avec les types.)
En C #, beaucoup de bibliothèques abusent de la Type object.GetType()
méthode astucieuse , pour le meilleur ou pour le pire.
Partout où vous stockez l'énumération en tant que valeur, vous pouvez vous demander si vous pouvez le stocker en Type
tant que valeur à la place.
Pour utiliser cette astuce, vous pouvez initialiser deux tables de hachage en lecture seule, à savoir:
// For disambiguation, I'll assume that the actual
// (behavior-implementing) classes are under the
// "Lang" namespace.
static readonly Dictionary<Type, DeclarationType> TypeToDeclEnum = ...
{
{ typeof(Lang.Procedure), DeclarationType.Procedure },
{ typeof(Lang.Function), DeclarationType.Function },
{ typeof(Lang.Property), DeclarationType.Property },
...
};
static readonly Dictionary<DeclarationType, Type> DeclEnumToType = ...
{
// same as the first dictionary;
// just swap the key and the value
...
};
La justification finale pour ceux qui suggèrent des classes et une hiérarchie d'héritage ...
Une fois que vous pouvez voir que les énumérations sont une approximation de la hiérarchie d'héritage , les conseils suivants sont valables:
- Concevez (ou améliorez) d'abord votre hiérarchie d'héritage,
- Revenez ensuite en arrière et concevez vos énumérations pour approximer cette hiérarchie d'héritage.
DeclarationType
. Si je veux déterminer s'il s'agit ou non d'x
un sous-typey
, je vais probablement vouloir écrire cela commex.IsSubtypeOf(y)
, pas commex && y == y
.