Requirements
After reading your question, I found the following requirements of the zoo:
- Search for animals by their species name
- Define Species Order
- Easy to add animals in the future
1. Search for animals by their species name
As you mentioned, there is an undesirable separation between the string "LION" and the class Lion . In Effective Java, paragraph 50, the following is indicated about strings:
Strings are bad replacements for other types of values . When part of the data enters the program from a file, from the network, or from the keyboard, it is often in string form. There is a natural tendency to leave it that way, but this tendency is justified only if the data is really textual in nature. If its numeric value, it must be converted to the corresponding numeric type, for example int , float or BigInteger . If his answer to the question is yes or no, he must be translated into boolean . More generally, if you have an appropriate type of value, whether primitive or object, you should use it; if not, you should write one. Although this advice may seem obvious, it is often broken.
Therefore, instead of looking for animals by their species, you should search for animals by their species.
2. Determine the order of species
To determine the order of views, you need to use a collection with a predictable iteration order, for example, the mentioned LinkedHashMap .
3. Easy to add animals in the future
Adding animals currently consists of the three steps you described. A side effect of using an enumeration is that only a person who has access to the source code has the ability to add new species (since the Species extension needs to be expanded).
Now let's look at Species.LION , this is a view of the Lion class. Note that this relationship semantically matches the relationship between a class and its instance. Therefore, a much more elegant solution would be to use Lion.class as a kind of Lion . It also reduces the number of steps to add an animal when you get this species for free.
Analysis of other parts of the code
Zoo
In its proposal, the zoo is responsible for the creation of animals. The consequence of this is that every zoo ever created must use all the specific animals (due to the Species enumeration) and use the specified ordering, there would be no change among the zoos. It is better to separate the creation of animals from the zoo in order to provide greater flexibility for both animals and the zoo.
Animal
Because of their flexibility, interfaces should be preferable to abstract classes. As explained in Effective Java, paragraph 18:
Clause 18. Prefers interfaces for abstract classes
The Java programming language provides two mechanisms for determining a type that allows multiple implementations: interfaces and abstract classes. The most obvious difference between the two mechanisms is that abstract classes are allowed to contain implementations of some methods, but interfaces are not. The more important difference is that to implement the type defined by an abstract class, the class must be a subclass of the abstract class. Any class that defines all the necessary methods and obeys the general contract is allowed to implement the interface, regardless of where the class is in the class hierarchy. Because Java allows only one inheritance, this restriction on abstract classes severely limits their use as type definitions.
Circular links
In your question, you mentioned that the animal should also have a link to the zoo in which it lives. This introduces a circular link that should be avoided as much as possible.
One of the many drawbacks of circular links:
Circular class references create high linkage; both classes must be recompiled every time either of them changes.
Implementation example
public interface Animal {} public class Zoo { private final SetMultimap<Class<? extends Animal>, Animal> animals; public Zoo() { animals = LinkedHashMultimap.create(); } public void addAnimal(Animal animal) { animals.put(animal.getClass(), animal); } @SuppressWarnings("unchecked") // the cast is safe public <T extends Animal> Set<T> getAnimals(Class<T> species) { return (Set<T>) animals.get(species); } }
Using
static class Lion implements Animal {} static class Zebra implements Animal {} final Zoo zoo = new Zoo(); zoo.addAnimal(new Lion()); zoo.addAnimal(new Zebra()); zoo.addAnimal(new Lion()); zoo.getAnimals(Lion.class); // returns two lion instances zoo.getSpeciesOrdering(); // returns [Lion.class, Zebra.class]
Discussion
In the above implementation, there is support for several animal specimens for each species, since this seems to make more sense for the zoo. If only one animal is required, consider using the Guava ClassToInstanceMap instead of SetMultimap.
The creation of animals was not considered as part of the design problem. If you need to build more complex animals, consider the builder pattern .
Adding an animal is now as simple as creating a new class that implements the Animal interface and adds it to the zoo.