web-dev-qa-db-fra.com

Créer une méthode d'usine dans Java qui ne repose pas sur 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 toute la question de savoir si la liste des classes augmente. Je pense que j'ai besoin d'avoir deux méthodes qui enregistre des chaînes aux classes et autres qui renvoient la classe en fonction de la chaîne de l'action.

Quelle est une bonne façon de faire cela en Java?

40
Brad

Ce que vous avez fait est probablement la meilleure façon d'y aller jusqu'à ce qu'un commutateur de chaîne soit disponible. (EDIT 2019 : Un commutateur sur la chaîne est disponible - utilisez cela.)

Vous pouvez créer des objets d'usine et une carte des chaînes à celles-ci. Mais cela obtient un TAD Verbose dans 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();
}

Au moment où cette réponse a été écrite à l'origine, les fonctionnalités destinées à JDK7 pourraient rendre le code comme ci-dessous. Comme il s'est avéré, Lambdas est apparu dans Java SE 8 et, pour autant que je sache, il n'y a pas de plan pour les littéraux de la carte. (édité 2016)

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

EDIT 2019 : Actuellement, cela ressemblerait à quelque chose comme ça.

import Java.util.function.*;
import static Java.util.Map.entry;

private static final Map<String,Supplier<Animal>> factoryMap = Map.of(
    "Meow", Cat::new, // Alternatively: () -> new Cat()
    "Woof", Dog::new // Note: No extra comma like arrays.
);

// For more than 10, use Map.ofEntries and Map.entry.
private static final Map<String,Supplier<Animal>> factoryMap2 = Map.ofEntries(
    entry("Meow", Cat::new),
    ...
    entry("Woof", Dog::new) // Note: No extra comma.
);

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

Si vous souhaitez ajouter un paramètre, vous devez basculer Supplier _ _ Factory (et get devient apply qui n'a aucun sens dans le contexte ). Pour deux paramètres BiFunction. Plus de deux paramètres, et vous êtes de retour pour essayer de le rendre lisible à nouveau.

53

Si vous ne le faites pas avez Pour utiliser des chaînes, vous pouvez utiliser un type d'enum pour les actions et définir une méthode abstraite.

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

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

    public abstract Animal getAnimal();
}

Ensuite, vous pouvez faire des choses comme:

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

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

12
Jeff

Utilisez des 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 la rétentionPolicy est exécutée, nous y accéderons via une réflexion.

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

package animal;

public abstract class Animal {

    public abstract String greet();

}

Étape 3. Créez 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 ex. Traiter les exceptions vérifiées méchantes.
[.____] Dans cette usine, j'ai utilisé la bibliothèque reflets .
[.____] J'ai fait cela le moyen difficile, c'est-à-dire que je n'ai pas fait de projet maven et je devais ajouter les dépendances manuellement.
[.____] Les dépendances sont:

Si vous avez sauté l'étape 2, vous devez modifier la méthode d'usine pour renvoyer l'objet.
( "Animal"), et laissez le constructeur non-args par défaut visible, puis l'usine instancera vos cours pour vous sans devoir être changé lui-même.

Voici la sortie:

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

Je n'ai pas essayé cela, mais pourriez créer un Map avec "MIOW", etc. comme clé et (disons) Cat.class comme valeur.

Fournir une génération d'instance statique via une interface et appelez comme

Animal classes.get("Meow").getInstance()
5
Rui Vieira

Je chercherais à extraire une représentation enum de la chaîne et de passer à autre chose.

4
rich

Vous avez déjà sélectionné la réponse à cette question, mais cela pourrait toujours aider.

Bien que je sois un développeur .NET/C #, c'est vraiment un problème général OOP. J'ai couru dans le même genre de problème et j'ai trouvé une belle solution (je pense) à l'aide d'un Conteneur IOC.

Si vous n'en utilisez pas encore un, c'est probablement une bonne raison de commencer. Je ne connais pas les conteneurs de COI de Java, mais je suppose qu'il doit y en avoir un avec des caractéristiques similaires.

Ce que j'avais était une usine contenant une référence au conteneur de la COI, qui est résolu par le conteneur lui-même (dans la bootstrapper)

...
public AnimalFactory(IContainer container) 
{ 
    _container = container; 
}

Vous pouvez ensuite configurer votre conteneur COI pour résoudre les types corrects en fonction d'une touche (le son de votre exemple). Cela résoudrait complètement les classes concrètes que votre usine doit revenir.

en fin de compte, votre méthode d'usine est réduite à ceci:

...
public Createable CreateAnimal(string action) 
{ 
    return _container.Resolve<Createable>(action); 
} 

Cette question Stackoverflow illustre le même type de problème avec les éléments du monde réel et la réponse validée montre un brouillon de ma solution (pseudo code). J'ai écrit plus tard n poteau de blog avec les vrais morceaux de code où il est beaucoup plus clair.

J'espère que cela peut aider. Mais cela pourrait être surchargé dans des cas simples. J'ai utilisé cela parce que j'ai eu 3 niveaux de dépendances à résoudre et qu'un conteneur de CIO assemble déjà tous mes composants.

2
Stéphane

Et qu'est-ce que les gens pensent d'utiliser classe.newinstance () Réponse de Tom Hawtin? Cela nous évitera de stocker des classes anonymes inutiles en mémoire? Plus le code sera plus propre.

Cela ressemblera à quelque chose comme ça:

private static final Map<String,Class> factoryMap =
    Collections.unmodifiableMap(new HashMap<String,Class>() {{
        put("Meow", Cat.class);
        put("Woof", Dog.class);
}});

public Animal createAnimal(String action) {
    return (Animal) factoryMap.get(action).newInstance();
}
1
goRGon

Ma pensée serait de mapper une ficelle à une fonction. De cette façon, vous pouvez passer Meow sur la carte et renvoyer la fonction constructrice déjà cartographiée. Je ne sais pas comment faire cela en Java, mais une recherche rapide retournée ceci SO Thread . Quelqu'un d'autre peut avoir une meilleure idée, cependant.

1
Dave McClelland