3
votes

Comment fournir des implémentations pour les valeurs enum en Java?

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.


3 commentaires

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 une RuntimeException pour exprimer ce fait.


J'apprécie le retour rapide!


8 Réponses :


2
votes

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()); 
}


2 commentaires

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.



0
votes

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 )


0 commentaires

0
votes

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 map = new EnumMap (DateType.class); et puis utilisez getOrDefault pour vous assurer que vous obtiendrez une sortie.


0 commentaires

5
votes

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!


4 commentaires

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



2
votes

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
    }
}


1 commentaires

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.



1
votes

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();
}


0 commentaires

1
votes

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


1 commentaires

Bonne réponse ;-)



0
votes

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); 
}


0 commentaires