I very often get myself in a situation where I need a different behavior of a component which depends on the concrete type of a different Interface.
To illustrate my question, I have written a small code piece which will show what I mean. In this Example I’m trying to instantiate an food object from an animal object.
interface Animal {
void growl();
String getAnimalType(); //"DOG" or "FISH"
}
abstract class Food {}
Meat extends Food {}
Bread extends Food {}
class FoodFactory {
Food createFoodForAnimal(Animal animal) {
switch (animal.getAnimalType()) {
case "DOG":
Food food = new Meat();
break;
case "FISH":
Food food = new Bread();
break;
default:
return;
}
}
}
Animal dog = new Dog();
Animal fish = new Fish();
FoodFactory factory = new FoodFactory();
Food food = factory.createFoodForAnimal(dog)
Food food = factory.createFoodForAnimal(fish)
To me this feels somewhat dirty, because is looks like a cast in disguise.
8
Tell, don’t ask might help you out here.
switch (animal.getAnimalType()) {
case "DOG":
Food food = new Meat();
break;
case "FISH":
Food food = new Bread();
break;
default:
return;
}
Notice what this code fragment is – a switch on behavior. That’s a “code smell” in an object oriented solution. Usually, it indicates that there is an underlying object that you haven’t discovered. Here, it might be more accurate to say that you haven’t delegated the responsibility to the objects you have already discovered.
interface Animal {
void growl();
void orderDinner(DinnerOrder order);
}
class Dog implements Animal {
//...
void orderDinner(DinnerOrder order) {
order.add(new Meat());
}
}
class Fish implements Animal {
//...
void orderDinner(DinnerOrder order) {
order.add(new Bread());
}
}
Actually, it probably doesn’t quite make sense to specify the instance of the food that the animal wants – it probably doesn’t care about a specific Bread entity, so much as Bread as a type. So maybe that gets deferred to the DinnerOrder
object.
So you might implement DinnerOrder
with methods that allow an animal to specify the kind of food that it wants, and free the Animal from knowing the details (encapsulation).
class Fish implements Animal {
//...
void orderDinner(DinnerOrder order) {
order.addBread();
}
}
Hard wiring in a specific command isn’t readily generalized. So it might be that you want to use arguments to specify the food after all. But as noted above, we probably don’t care which Bread entity is dinner tonight; in particular, we might want to give the Animals a special treat at Christmas; so the animals specify what they want, and then the provider does its best.
class Fish implements Animal {
//...
void orderDinner(DinnerOrder order) {
order.add(BREAD);
}
}
Of course, if you think preferredFood is a trait that a number of Animals have, then you might look to treat the food, not as a static constant, but as a property of an instance.
class Fish implements Animal {
//...
void orderDinner(DinnerOrder order) {
order.add(this.preferredFood);
}
}
This is a very common OO pattern; a method that passes a copy of its state to an argument for further processing. For instance, this is how DomainServices
typically work in a domain model — the service is passed to an aggregate, which supplies its own state back to the service for work.
Specifications are sometimes used to make this even more general. Instead of passing a property for the DinnerOrder to interpret, you can pass a predicate that identifies what foods have that property, and then let the receiver investigate the alternatives available.
Specification<Food> FISH_FOOD = new Specification {
boolean isSatifiedBy(Food food) {
return food.isA(BREAD);
}
}
class Fish implements Animal {
//...
void orderDinner(DinnerOrder order) {
order.order(FISH_FOOD);
}
}
Used in this way, the Specification is an example of the Strategy pattern; which might be a better fit if you wanted to give the Animals a way to rank/choose which of the available options they would prefer….
3
There are two possible approaches here.
The simplest way to solve this if every Animal
knows itself what they are supposed to be fed. So:
interface Animal {
Food wantedFood();
}
This might completely solve your problem, and if so, is a good solution.
But while this is simple, this is often unsatisfactory: your Animal
interface will grow and grow with marginally related methods. Or maybe we don’t want each animal to feed itself, and rather want a separate ZooKeeper
to handle this. A ZooKeeper
knows how to handle()
each Animal
. How does the ZooKeeper
know which animal they are handling? There’s a technique for this called double dispatch, where the Animal is responsible for asking the ZooKeeper for correct handling. This looks very much like the above example where the Animal itself knows what food we want, but once we have a generic ZooKeeper we can have different ZooKeepers with different behaviours:
interface ZooKeeper {
handleDog(Dog dog);
handleFish(Fish fish);
}
interface Animal {
accept(ZooKeeper keeper);
}
class Dog implements Animal {
accept(ZooKeeper keeper) { keeper.handleDog(this); }
}
class Fish implements Animal {
accept(ZooKeeper keeper) { keeper.handleFish(this); }
}
class FeedingZooKeeper implements ZooKeeper {
handeDog(Dog dog) { /* the dog gets meat */ }
handeFish(Fish fish) { /* the fish gets bread */ }
}
Animal someAnimal = ...;
FeedingZooKeeper keeper = new FeedingZooKeeper();
someAnimal.accept(keeper); // either calls handleDog() or handleFish()
We can now add other ZooKeepers without having to change the Animals.
This is generally known as the visitor pattern, and allows us to implement a function differently for different types, even if we don’t know which kind of type we’ll get at run time. However, there is a large trade off here: if the zoo gets a new Animal, we have to update the ZooKeeper interface, and all ZooKeeper implementations with it. This is bad, because this makes it hard to extend, but also good because it makes it more difficult to forget to handle a case.
9
Note: If your actual code resembles the Animal/Food example, please go with the suggestions in the other answers that cover the double dispatch/visitor pattern technique.
The answer to more general question, “Is this ever appropriate?”, is a qualified yes. One of the times when these sort of methods are appropriate is when implementing algebraic data types in a language that doesn’t provide pattern matching syntax. While algebraic data types are more of a functional programming technique, they do crop up from time to time in object oriented code.
The basic idea is pretty simple, algebraic data types are composite types. The key that makes returning an enum to identify them a reasonable idea is that there is a fixed number of implementations of the interface, which by the nature of the type will never be expanded.
A good example is if you need a type that would allow a method to accept or return one of two types. We’ll call this type Or
. A very minimal Java implementation might look like this:
public enum OrSubType {
LEFT,RIGHT
}
interface Or<L,R> {
OrSubType type();
L left();
R right();
}
final class Left<L,R> extends Or<L,R> {
final private L value;
public Left(L value) {
this.value = value;
}
public OrSubType type() { return LEFT; }
public L left() { return value; }
public R right() {
throw new IllegalStateException(
"Attempted to get a Right from a Left(" + value + ")"
);
}
}
final class Right<L,R> extends Or<L,R> {
final private R value;
public Right(R value) {
this.value = value;
}
public OrSubType type() { return RIGHT; }
public R right() { return value; }
public L left() {
throw new IllegalStateException(
"Attempted to get a Left from a Right(" + value + ")"
);
}
}
That’s quite a lot of extra code, but it will allow you to do stuff like this:
Or<Value,ErrorCode> result = doOperationThatCanFail();
switch(result.type()) {
case LEFT:
doSomethingWithResult(result.left());
break;
case RIGHT:
displayErrorMessageForCode(result.right());
break;
}
If the language in question has pattern matching as first class syntax, there’s often help provided to make this easier to use. In Scala the above example class would be simply this:
sealed trait Or[L,R] {
def left: L
def right: R
}
object Or {
case class Left[L](var left: L) extends Or[L,Nothing] {
def right: Nothing = throw new IllegalStateException(
s"Attempted to get a Right from a Left($left)"
)
}
case class Right[R](var right: R) extends Or[Nothing,R] {
def left: Nothing = throw new IllegalStateException(
s"Attempted to get a Left from a Right($left)"
)
}
}
The sample usage is also simplified:
val result: Or[Value,ErrorCode] = doOperationThatCanFail()
result match {
case Or.Left(value) => doSomethingWithResult(value);
case Or.Right(errorCode) => displayErrorMessageForCode(errorCode);
}
4
This strikes me as very similar to dependency injection, the only difference being that a dependency is only set once and an animal may be fed multiple times. My preferences (in order) would be…
- Let the animals create their own food (only possible if food is easy to create).
- Create a type-specific food factory (e.g. a
MeatFactory
for aDog
) and pass that into the animal when you create it, using dependency injection if necessary. Then there can be just aneat()
method where the animal grabs the food from the factory and chows down. - If not, and there are some common resources/ingredients necessary to create food, then pass those into the
feed()
method instead. - If there is no real commonality between the foods, then create a bucket of foods (a refrigerator, or DoI container if you prefer), and give that to the animals to take out what food they want to eat.
Thinking on it a bit more I suppose it would depend on the animal’s primary purpose. If the primary purpose is not really food related (e.g. entertaining people, breeding, etc.) and food is just something it needs to survive/do its job then the above options are what I would go with.
However, if the animal’s primary purpose is to destroy food (or process it, e.g. creating fertilizer) then the above may be a good fit but there may be other patterns.
You may have a router of sorts, routing foods to the correct animals. The thing that leaps to mind is the URL routing mechanism used by technologies like JAX-RS. They have a registry of services which map to different URLs. Such a thing might be implemented by a Set
of Animal
where each animal has a canEat()
method and you just cycle through each animal in your menagerie until you find one that can eat the food you need eaten. Or you might do that with a HashMap
. These will be pretty similar to your original design but slightly more extensible (since there is no switch statement to update).
If the set of constants is somewhat small without much potential to grow exponentially larger, I consider it acceptable. I would strongly recommend using an enum return type, rather than a string, to allow the compiler to catch errors before runtime.
Your initial approach may seem less than desirable in part because you are introducing an unnecessary coupling between the FoodFactory and Animal classes. Consider the below alternative, where the createFoodForAnimal parameter has changed from Animal to AnimalType. Taking this approach offers a couple of benefits:
1) The FoodFactory can now be tested without also needing to
instantiate an Animal.
2) The code clarity has improved slightly
public enum AnimalType {
FISH, LIGER
}
class FoodFactory {
Food createFoodForAnimal(AnimalType animalType) {
Food food;
switch (animalType) {
case LIGER:
food = new UnicornMeat();
break;
case FISH:
food = new Bread();
break;
default:
food = new Spam();
}
return food;
}
}
Having some of the other responses, I would also add that keeping the decision logic separate from the Animal or ZooKeeper class can greatly improve the flexibility of your system. For instance, consider needing to make a change where all animals eat bread at a particular time of the day. You can make a much less obtrusive modification to the FoodFactory than you can to all of the Animal classes.
2
You can think in terms of composable behaviours using DI. You already have all the exising pieces in hand, just tweak it a little bit:
// Your FoodFactory interface
public Food createFood();
// Factories, each of them returns a specific kind of food
meat = new MeatFactory();
fish = new FishFactory();
poop = new PoopFactory();
// Inject the wanted factory to the animal to have the desired behaviour
cat = new Cat(fish);
dog = new Dog(poop);
tiger = new Tiger(meat);
lion = new Lion(meat);
- It’s closed for modification, as opposed to your first example.
- Several animals can use the same food factory, without extra code
- You “wire” your objects as you like, building the behaviour you want, in a relative flexible way.
- The animal asks the factory for the food without knowing what it is
- The food factory doesn’t know about animals.
Extra: you can make your food factories smarter if needed:
cat = new Cat(
new RotatingFood(fish, meat)
);
dog = new Dog(
new RandomFood([meat, poop, fish, shoe])
);
// RandomFood & RotatingFood both implement FoodFactory and use food factories