1

I'm working on an example of a Zoo that has several different types of Animal. The user enters commands such "add tiger" and then a Tiger is added to the Zoo.

In my command interpreter class I have some code like this:

String animalName...
Animal newAnimal;
if (animalName.equals("tiger"))
    newAnimal = new Tiger();
else if (animalName.equals("elephant"))
    newAnimal = new Elephant();

The problem here is that when a new kind of animal is added to the program, this piece of code must also be changed. I'd like to add new animals just by subclassing Animal without changing anything in the already existing classes.

The name given by the user in her command isn't necessarily identical to the class name of the animal (for example, "add Bengal tiger" would add a BengalTiger object).

If possible, I'd prefer to avoid using reflection.


This is the final code:

private static String getClassName(String name) {
    char ch;
    int i;
    boolean upper = true;
    StringBuilder s = new StringBuilder("animals.");

    for (i=0; i<name.length(); i++) {
        ch = name.charAt(i);
        if (ch!=' ') {
            if (upper)
                s.append(Character.toUpperCase(ch));
            else
                s.append(Character.toLowerCase(ch));
            upper = false;
        } else
            upper = true;

    }
    return s.toString();
}

@Override
public Animal addAnimal(String s) {
    try {
        Animal animal = (Animal)Class.forName(getClassName(s)).newInstance();
        addAnimal(animal);
        return animal;
    } catch (InstantiationException e) {
        throw new IllegalArgumentException("There are no animals called like this");
    } catch (IllegalAccessException e) {
        throw new IllegalArgumentException("There are no animals called like this");
    } catch (ClassNotFoundException e) {
        throw new IllegalArgumentException("There are no animals called like this");
    } catch (ClassCastException e) {
        throw new IllegalArgumentException("There are no animals called like this");
    }
}
4

5 回答 5

3

You should use dynamic class loading: Class.forName(name), e.g.:

String animal = ... // e.g. elephant
String className = "com.mycompany.animals." + animal.substring(0, 1).toUpperCase() + animal.subString(1);
Animal obj = (Animal)Class.forName(className);
于 2012-11-29T22:20:03.237 回答
2

You need to use Class.forName(X) to make this happen. Class.forName will help you get the load the actual class as string value. Refer to http://docs.oracle.com/javase/1.4.2/docs/api/java/lang/Class.html#forName(java.lang.String).

于 2012-11-29T22:17:42.133 回答
1
Object animalObj = Class.forName(animalString);

But it has to be the exact name (with the same capitalization).

于 2012-11-29T22:17:32.250 回答
1

For completeness sake, here's rather verbose solution based on AbstractFactory pattern. You will need to subclass two interfaces instead of one (animal type's factory and animal type itself), but, hey, there's no reflection!

interface Animal {
    String whoAmI();
}

class Tiger implements Animal {
    @Override
    public String whoAmI() {
        return "Tiger";
    }
}

interface AnimalFactory {
    Animal create();
}

abstract class AbstractAnimalFactory implements AnimalFactory {
    protected AbstractAnimalFactory(String animalName, FactoriesRegistry registry) {
        registry.register(animalName, this);
    }
}

class TigersFactory extends AbstractAnimalFactory {
    TigersFactory(FactoriesRegistry registry) {
        super("tiger", registry);
    }

    @Override
    public Animal create() {
        return new Tiger();
    }
}

class FactoriesRegistry {
    HashMap<String, AnimalFactory> byAnimalName = new HashMap<String, AnimalFactory>();

    Animal produce(String id) {
        AnimalFactory factory = byAnimalName.get(id);
        if (factory != null)
            return factory.create();
        else
            throw new IllegalArgumentException("Unknown animal " + id);
    }

    public void register(String animalName, AnimalFactory factory) {
        byAnimalName.put(animalName, factory);
    }
}

public class SOSample {
    public static void main(String[] args) {
        FactoriesRegistry registry = new FactoriesRegistry();
        TigersFactory tigersFactory = new TigersFactory(registry);
        System.out.println(registry.produce("tiger").whoAmI());
    }
}
于 2012-11-29T22:29:25.827 回答
0

Reflection isn't the only Solution for this Problem like @Victor Sorokin mentioned. But it's the most simple/clean one to code!!!

Solution with reflection:

public interface Animal {
    // put your contract here
}

public class AnimalFactory {
    public static final AnimalFactory INSTANCE = new AnimalFactory();

    private Map<String, Class<? extends Animal>> animalTypeMap;

    private AnimalFactory() {};

    public Animal createAnimal(String key) {
        Animal animal = null;
        if (animalTypeMap != null) {
            Class<? extends Animal> animalType = animalTypeMap.get(key);
            if (animalType != null) {
                try {
                    animal = animalType.newInstance();
                } catch (Throwable error) {
                    throw new RuntimeException("Unable to create animal with key='" + key + "'", error);
                }
            }
        }
        return animal;
    }

    public Map<String, Class<? extends Animal>> getAnimalTypeMap() {
        if (animalTypeMap == null) {
            animalTypeMap = new HashMap<String, Class<? extends Animal>>();
        }
        return animalTypeMap;
    }

    public void setAnimalTypeMap(Map<String, Class<? extends Animal>> animalTypeMap) {
        this.animalTypeMap = animalTypeMap;
    }

    public void addAnimalType(String key, Class<? extends Animal> animalType) {
        getAnimalTypeMap().put(key, animalType);
    }

    public void removeAnimalType(String key, Class<? extends Animal> animalType) {
        if (animalTypeMap != null) {
            animalTypeMap.remove(key);
        }
    }    
}

And then, you use it like this:

Animal myAnimal = AnimalFactory.INSTANCE.createAnimal("myAnimalKey");

With this solution you just need to fill in that Map with the Animal types you need! The other solution it's also a clean/good solution, but is very verbose...

于 2012-11-29T22:47:54.683 回答