You could come up with something like this

However, there is a better solution. You can see here that this design consist of 7 classes and one interface. In this diagram, Cat
and Dog
could (and probably should) be an abstract class. The drawback to this design is "class explosion". Supposed you need to add a WildDog
. You will need to create another class, for starters. And there could be a lot of common code among classes because some of these classes might do similar things that you can't put on the parent class.
The best solution is to create basic classes that can be easily modified by injecting the behaviors you want particular instances to have. For example, to create an organic cat and a robotic cat, you could simply create a Cat
and inject the desired behavior to the instance in order for it to assume the injected behavior.

On the surface, this doesn't look much of an improvement, since the number of classes were only reduced by one. But if you were to add one more behavior (i.e. WildAnimalBehavior
), it will save you from creating two animal classes. Also, if you were to add 5 more animals, you can imagine how much more complex this class hierarchy will get. The main idea of this design is to add behavior to objects rather than creating classes just to model some behavior. In fact, this design could allow creating an object with some hybrid behavior. For example, instead of creating a CyborgCat
class, you could simply inject robotic and organic behavior to a Cat
instance (thru a new CyborgBehavior
class).
public abstract class Animal {
protected abstract void makeNoise();
protected abstract void eat();
}
public class Cat extends Animal {
private Behavior behavior;
public Cat(Behavior behavior) {
this.behavior = behavior;
}
@Override
public void makeNoise () {
behavior.makeNoise();
}
@Override
public void eat () {
behavior.eat();
}
}
public interface Behavior {
void makeNoise();
void eat();
}
public class OrganicBehavior implements Behavior {
@Override
public void makeNoise () {
System.out.println(this.getClass().getSimpleName() + ": I am an organic being. I make a natural sound!");
}
@Override
public void eat () {
System.out.println(this.getClass().getSimpleName() + ": Thank you! I require nourishment to live.");
}
}
public class RoboticBehavior implements Behavior {
@Override
public void makeNoise () {
System.out.println(this.getClass().getSimpleName() + ": beep beep... dit dot beep..");
}
@Override
public void eat () {
System.out.println(this.getClass().getSimpleName() + ": I do not require food.");
}
}
public class DependencyInjectionDemo {
public static void main (String[] args) {
Cat roboticCat = new Cat(new RoboticBehavior());
Cat organicCat = new Cat(new OrganicBehavior());
roboticCat.makeNoise();
roboticCat.eat();
organicCat.makeNoise();
organicCat.eat();
}
}
The output:
RoboticBehavior: beep beep... dit dot beep..
RoboticBehavior: I do not require food.
OrganicBehavior: I am an organic being. I make a natural sound!
OrganicBehavior: Thank you! I require nourishment to live.
Although I used this example (based on your original model), a better way to use DI is to create behaviors for each of the methods or family of methods (methods are the behavior of an object). For example, EatingBehavior
, NoiseBehavior
, etc. This way, you could mix and match behaviors better. For example, two instances of Cat
could be created to display robotic behavior. However, maybe one instance is more advanced and it's capable of making "organic cat" noises rather than basic robotic noises and still not require food just like other robotic cats. Separating behaviors in that matter will allow you to make such adjustments to your code without creating another class to display such "hybrid" behavior.
I hope you found this example useful. By the way, this is called "Strategy Pattern". You should search the web for that topic as well as "Dependency Injection" to learn more. In real-world applications, I imagine that games like Apex Legends, Skyrim, and Call of Duty (among others) use this design patterns to apply "skins" to base characters. In Skyrim, when creating a character, the character contains some basic attributes. When you are "creating" or "designing" your character, the attributes change as you select ("inject") new attributes to the character (i.e. skin color, scars, mouth shape, etc.). The same goes for other games when you select skins.