J'ai une classe enum avec des valeurs supposées croître avec le temps et je veux que les utilisateurs qui ajoutent de nouvelles valeurs enum fournissent également l'implémentation quelque part. Mais je ne suis pas sûr de savoir comment les forcer à fournir une implémentation, car celle-ci appartiendra à une autre classe. Par exemple
if (x.getDateType().equals(DayType.SUNDAY)) { ... }else if(x.getDateType().equals(DayType.MONDAY)){ .. }
Référé dans une classe
class X{ DateType dateType; .. }
Et utilisé dans une autre classe
public enum DayType { SUNDAY, MONDAY; }
Donc, si quelqu'un ajoute DateType, il devrait également être forcé d'ajouter l'implémentation dans la logique if-else ci-dessus. De préférence en ajoutant des interfaces fonctionnelles si possible?
Je ne peux pas forcer l'implémentation dans la classe enum car l'implémentation a des dépendances de ressort.
8 Réponses :
Bien que je doute que cela puisse être restreint au moment de la compilation. Un moyen plus propre d'implémenter quelque chose de similaire serait d'utiliser switch
ici:
switch (x.getDateType()) { case MONDAY: // do something break; case SUNDAY: // do something break; default: throw new CustomException("No implementations yet for :" + x.getDateType()); }
Votre ingénieur d'exploitation appréciera grandement que vous ajoutiez également le nom du type non géré au message CustomException
;)
Ce n'est pas une solution orientée objet. Plus orienté objet est d'utiliser le polymorphisme par exemple en appliquant le modèle de stratégie.
Une énumération était censée contenir les valeurs finales (statiques) au moment de la compilation, donc ajouter dynamiquement plus de valeurs est impossible (il suffit de le mettre là-bas). Quant à l'autre partie de votre question, comme @nullpointer l'a souligné, il est préférable d'utiliser la clause switch
. Outre l'amélioration de la clarté du code, le compilateur avertit s'il y a des valeurs d'énumération pour lesquelles aucune instruction case
n'est déclarée (c'est-à-dire, étant donné que vous omettez default
) p >
Je ne suis pas non plus sûr que la conception d'énumérations pour l'extension par l'utilisateur soit une bonne idée. C'est statique, comme l'a déclaré @Garikai. Cela conduira à une odeur de code.
Si vous souhaitez avoir une option facile pour étendre le mappage d'énumération à fonction, je proposerais Map
et puis utilisez getOrDefault
pour vous assurer que vous obtiendrez une sortie.
La vraie réponse est: ne faites pas cela.
Si vous avez un "type" et que vous savez que ce "type" verra de nouvelles incarnations au fil du temps, qui nécessitent un comportement différent, alors la réponse est d'utiliser une classe abstraite et un polymorphisme.
Peu importe que vous utilisiez des chaînes if / else ou des instructions switch: l'idée que vous avez quelque chose comme:
if (someObject.getSomething() == whatever) { then do this } else { that }
est une mauvaise pratique!
Si c'est le cas, vous devriez utiliser une telle instruction switch dans une fabrique pour renvoyer différentes instances de sous-classes, pour ensuite appeler des méthodes "courantes" sur de tels objets.
Votre approche actuelle est A) d'externaliser les connaissances sur l'état interne et B) d'appliquer également les dépendances de plusieurs manières.
C'est ainsi que le code spaghetti commence! Mieux vaut prendre du recul et réfléchir à d'autres moyens de résoudre ce problème en POO!
Je suis d'accord avec cet article, cependant, j'imagine que le problème se pose toujours pour une usine. Maintenant c'est une question de "Comment gérer l'ajout de méthodes de fabrique à tous les endroits appropriés quand un nouveau type 'input' est imaginé?". Indépendamment de l'abstraction, je pense qu'une réponse appropriée est de programmer de manière défensive en lançant une exception qui informe l'utilisateur du cas d'utilisation manquant.
@flakes Le but de la fabrique est de cacher ce commutateur laid autant que possible. Parfois, vous ne pouvez pas l’éviter complètement, mais l’idée serait que les parties "essentielles" sont dans différentes classes étendant la classe de base, et alors vous avez exactement une méthode de fabrique qui fait peut-être ce genre de switch, pour renvoyer des instances d'objets sur lesquels vous pouvez appeler des méthodes.
En fait, je pense avoir résolu ce problème avec l'usine ou du moins j'ai offert une option raisonnable pour l'usine sans clause if-else. L'usine stocke les implémentations dans la carte, ce qui signifie un accès O (1). Voir ma réponse à cette question et les références aux articles sur la bibliothèque open source MgntUtils
aussi je suis d'accord et j'aime votre réponse - la voter
Une alternative à l'option de nullpointer est de mettre cette logique dans l'énumération elle-même, en supposant que cela a du sens dans votre conception (si cette responsabilité peut résider avec le type de jour enum).
x.getDateType().processSomeAction(inputIfApplicable);
Cela garantira que la nécessité de fournir une implémentation spécifique à la journée est évidente pour les développeurs qui apportent des modifications à DayType
.
Et l'appelant aura juste besoin de:
public enum DayType { SUNDAY { @Override void processSomeAction(Object input) { super.processSomeAction(input); // process action with SUNDAY-specific logic } }, MONDAY; //can be made abstract if there's no default implementation void processSomeAction(Object input) { // process action with default logic } }
C'est aussi une bonne solution mais devient rapidement peu maniable lorsqu'il existe de nombreux types et que la logique est conséquente. Fonctionne parfaitement pour les cas relativement simples.
Vous pouvez créer une interface qui sera implémentée par votre classe Enum. Dans ce cas, toutes les énumérations doivent implémenter la méthode dans l'interface.
public enum DateType implements DateTypeInterface { SUNDAY { @Override public void checkCondition() { System.out.println("Implement Sunday logic"); } }, MONDAY { @Override public void checkCondition() { System.out.println("Implement Monday logic."); } } } public interface DateTypeInterface { public void checkCondition(); }
Je ne pense pas que vous ayez une option au moment de la compilation pour appliquer votre politique. Ce que je suggérerais, c'est de créer une interface (quelque chose comme DateTypeAction
) avec une seule méthode (par exemple action ()
) et ensuite une Factory qui produit des implémentations concrètes basées sur une valeur de votre DateType
(quelque chose comme DateTypeActionFactory.getDateTypeAction (DateType dateType)
). Lors de votre phase d'initialisation, vous pouvez parcourir toutes les valeurs de votre DateType ( DateType.values ()
) et vérifier qu'il existe une implémentation non nulle de DateTypeAction
dans votre DateTypeActionFactory
pour chaque valeur. Si vous trouvez une implémentation manquante pour une ou plusieurs valeurs, lancez une exception avec un message d'erreur explicite indiquant qu'une ou plusieurs implémentations sont manquantes et échouent au démarrage de votre application. Cela semble un modèle raisonnable.
BTW si vous utilisez ce modèle, j'ai une recommandation: j'ai écrit une bibliothèque java open source appelée MgntUtils qui fournit un cadre simple (très bien adapté pour une utilisation avec Spring) qui a un modèle d'usine auto-remplissant. C'est à dire. vous pouvez créer une interface et une bibliothèque d'extension d'usine pour les classes parentes, puis chaque implémentation de votre interface sera automatiquement insérée dans votre usine avec un nom prédéfini. Je l'ai utilisé plusieurs fois et je l'ai trouvé très pratique. Voici le lien vers l'article décrivant l'ensemble de la bibliothèque: MgntUtils Bibliothèque Java Open Source avec filtrage de trace de pile, analyse Silent String, convertisseur Unicode et comparaison de version . Cherchez le paragraphe
Gestion du cycle de vie (usines auto-instanciées)
pour une brève explication de la fonctionnalité. Vous pouvez lire ici toute l’idée de la fonctionnalité: Accès non intrusif aux Beans "Orphaned" dans le framework Spring . Les deux articles expliquent également où trouver la bibliothèque, mais voici les liens directs: Maven Central Repository et Github . La bibliothèque est livrée avec javadoc bien écrit
Bonne réponse ;-)
Cette réponse se développe sur certaines des autres réponses ici promouvant l'utilisation du polymorphisme.
Ce que j'essaie généralement de faire pour éviter d'avoir un commutateur / conditionnel croissant quelque part, est d'ajouter une méthode d'identification à l'interface:
private DayProcessor[] dayProcessors = new DayProcessor[] {new MondayProcessor(), new SundayProcessor()};
Ensuite, je crée un composant d'usine avec toutes les implémentations de DayProcessor
disponibles câblées par Spring:
@Component public class DayProcessorFactory { @Autowired private List<DayProcessor> dayProcessors; public DayProcessor create(Day day) { for (DayProcessor dayProcessor: dayProcessors) { if (dayProcessor.day().equals(day)) { return dayProcessor; } } // DayNotFoundException extends RuntimeException throw new DayNotFoundException(String.format("No DayProcessor found for day %s", day)); } }
Ceci est une forme du modèle de stratégie .
Un Une autre façon de répertorier tous les jours est de les énumérer (mais vous devrez ensuite mettre à jour la liste lorsqu'un nouveau jour est créé):
public enum Day { SUNDAY, MONDAY } public interface DayProcessor { Day day(); // I don't know what Days are supposed to do exactly Object process(Object input); }
Je ne pense pas vraiment que vous puissiez (ou devriez) les forcer par programme. Est-il possible de simplement le configurer de manière propre (avec un chemin par défaut) afin que vous puissiez y lancer une exception appropriée indiquant au développeur qu'il doit faire l'implémentation?
Cela revient vraiment à la diligence du programmeur. Vous devez vous assurer qu'une branche "par défaut" ou "non implémentée" est toujours proposée lors de la gestion des actions pour vos valeurs
enum
. Si le cas "non implémenté" est une erreur de programmeur, alors lancez uneRuntimeException
pour exprimer ce fait.J'apprécie le retour rapide!