42 votes

Création d'une méthode d'usine en Java qui ne dépend pas de if-else

Actuellement, j'ai une méthode qui agit comme une usine basée sur une chaîne donnée. Par exemple :

public Animal createAnimal(String action)
{
    if (action.equals("Meow"))
    {
        return new Cat();
    }
    else if (action.equals("Woof"))
    {
        return new Dog();
    }

    ...
    etc.
}

Ce que je veux faire, c'est éviter tout le problème if-else lorsque la liste des classes s'allonge. Je pense que j'ai besoin de deux méthodes, une qui enregistre les chaînes de caractères dans les classes et une autre qui renvoie la classe en fonction de la chaîne de caractères de l'action.

Quelle est la meilleure façon de faire cela en Java ?

58voto

Tom Hawtin - tackline Points 82671

Ce que vous avez fait est probablement la meilleure façon de procéder, jusqu'à ce qu'un interrupteur sur la corde soit disponible.

Vous pourriez créer des objets d'usine et une carte de chaînes vers ces objets. Mais cela devient un peu verbeux dans le Java actuel.

private interface AnimalFactory {
    Animal create();
}
private static final Map<String,AnimalFactory> factoryMap =
    Collections.unmodifiableMap(new HashMap<String,AnimalFactory>() {{
        put("Meow", new AnimalFactory() { public Animal create() { return new Cat(); }});
        put("Woof", new AnimalFactory() { public Animal create() { return new Dog(); }});
    }});

public Animal createAnimal(String action) {
    AnimalFactory factory = factoryMap.get(action);
    if (factory == null) {
        throw new EhException();
    }
    return factory.create();
}

Dans JDK7, cela peut ressembler à quelque chose comme :

private interface AnimalFactory {
    Animal create();
}
private static final Map<String,AnimalFactory> factoryMap = {
    "Meow" : { -> new Cat() },
    "Woof" : { -> new Dog() },
};

public Animal createAnimal(String action) {
    AnimalFactory factory = factoryMap.get(action);
    if (factory == null) {
        throw EhException();
    }
    return factory.create();
}

14voto

bluedevil2k Points 1967

Il n'y a pas besoin de cartes avec cette solution. De toute façon, les cartes ne sont qu'une façon différente de faire une déclaration if/else. Tirez parti d'un peu de réflexion et ce ne sont que quelques lignes de code qui fonctionneront pour tout.

public static Animal createAnimal(String action)
{
     Animal a = (Animal)Class.forName(action).newInstance();
     return a;
}

Vous devrez changer vos arguments de "Woof" et "Meow" en "Cat" et "Dog", mais cela devrait être assez facile à faire. Cela évite tout "enregistrement" de chaînes de caractères avec un nom de classe dans une carte, et rend votre code réutilisable pour tout animal futur que vous pourriez ajouter.

12voto

Jeff Points 407

Si vous ne le faites pas ont pour utiliser des chaînes de caractères, vous pourriez utiliser un type d'énumération pour les actions, et définir une méthode de fabrique abstraite.

...
public enum Action {
    MEOW {
        @Override
        public Animal getAnimal() {
            return new Cat();
        }
    },

    WOOF {
        @Override
        public Animal getAnimal() {
            return new Dog();
        }
    };

    public abstract Animal getAnimal();
}

Alors tu peux faire des choses comme :

...
Action action = Action.MEOW;
Animal animal = action.getAnimal();
...

C'est un peu funky, mais ça marche. De cette façon, le compilateur se plaindra si vous ne définissez pas getAnimal() pour chaque action, et vous ne pouvez pas passer dans une action qui n'existe pas.

10voto

crowne Points 6002

Utilisez les scannotations !

Étape 1. Créez une annotation comme ci-dessous :

package animal;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface AniMake {
    String action();
}

Notez que le RetentionPolicy est exécutable, nous y accéderons par réflexion.

Étape 2. (Facultatif) Créez une super classe commune :

package animal;

public abstract class Animal {

    public abstract String greet();

}

Étape 3. créer les sous-classes avec votre nouvelle annotation :

package animal;

@AniMake(action="Meow")
public class Cat extends Animal {

    @Override
    public String greet() {
        return "=^meow^=";
    }

}
////////////////////////////////////////////
package animal;

@AniMake(action="Woof")
public class Dog extends Animal {

    @Override
    public String greet() {
        return "*WOOF!*";
    }

}

Étape 4. Créez l'usine :

package animal;

import java.util.Set;

import org.reflections.Reflections;

public class AnimalFactory {

    public Animal createAnimal(String action) throws InstantiationException, IllegalAccessException {
        Animal animal = null;
        Reflections reflections = new Reflections("animal");
        Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(AniMake.class);

        for (Class<?> clazz : annotated) {
            AniMake annoMake = clazz.getAnnotation(AniMake.class);
            if (action.equals(annoMake.action())) {
                animal = (Animal) clazz.newInstance();
            }
        }

        return animal;
    }

    /**
     * @param args
     * @throws IllegalAccessException 
     * @throws InstantiationException 
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        AnimalFactory factory = new AnimalFactory();
        Animal dog = factory.createAnimal("Woof");
        System.out.println(dog.greet());
        Animal cat = factory.createAnimal("Meow");
        System.out.println(cat.greet());
    }

}

Cette usine peut être nettoyée un peu, par exemple en gérant les méchantes exceptions vérifiées, etc.
Dans cette usine, j'ai utilisé le Réflexions bibliothèque.
J'ai fait cela à la dure, c'est-à-dire que je n'ai pas fait de projet maven et j'ai dû ajouter les dépendances manuellement.
Les dépendances sont :

Si vous avez sauté l'étape 2, vous devrez modifier la méthode factory pour qu'elle renvoie Object.
A partir de là, vous pouvez continuer à ajouter des sous-classes, et tant que vous les annotez avec AniMake (ou tout autre nom plus approprié que vous trouverez), que vous les placez dans le paquetage défini dans le constructeur de Reflections (dans ce cas "animal"), et que vous laissez le constructeur no-args par défaut visible, alors la fabrique instanciera vos classes pour vous sans avoir à être modifiée elle-même.

Voici le résultat :

log4j:WARN No appenders could be found for logger (org.reflections.Reflections).
log4j:WARN Please initialize the log4j system properly.
*WOOF!*
=^meow^=

5voto

Rui Vieira Points 2713

Je n'ai pas essayé, mais je pourrais avec créer une Map avec "Meow", etc. comme clés et (dire) Cat.class comme valeur.

Fournir une génération d'instance statique via une interface et l'appeler en tant que

Animal classes.get("Meow").getInstance()

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X