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
  • 4
    Why do you "prefer to avoid using reflection"? It's the solution to your problem. Commented Nov 29, 2012 at 22:15
  • Because reflection is very Java specific and I'd like a more technological independent solution. Commented Nov 29, 2012 at 22:41
  • ... but written in Java? Commented Nov 29, 2012 at 22:45
  • Yes, it is an example and I have to use some language, but the idea is to show some OOP design problems and solutions. Commented Nov 29, 2012 at 22:49

5 Answers 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);
Sign up to request clarification or add additional context in comments.

2 Comments

Wouldn't it be Animal a = (Animal)Class.forName(className).newInstance();?
Although it uses reflection, I'm using this solution due to its simplicity. The only problem I see here is that all animals must be in the same package, so it's not possible for someone to add a new package with some new kinds.
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).

Comments

1
Object animalObj = Class.forName(animalString);

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

Comments

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

4 Comments

Code still has to be changed when a new animal is added: someone has to write code which will instantiate the new ElephantFactory... no?
But here you need to modify main every time you add a new kind of animal, in order to create the factory.
Yes, but the fragment which creates animals remains unchanged.
Agreed. This is a good solution. And it follows the open/closed principle. You just have to code a new Factory for each type of Animal, rather than changing existing code to support new animal types.
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...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.