0

So we are doing a Virtual Pet project and we need to make ArrayLists and interact with the virtual pets.

What I want to do is have a PetClass superclass that extends to CatClass and DogClass.
CatClass will be a superclass to OrganicCat and RoboticCat. Same with the dog class.

Is this possible? Or should I use an Abstract method or Inheritance method?

I cannot find any questions or answers so I am assuming that this isn't possible.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • 1
    This is possible. However, one of the super classes MUST be an interface because Java only supports single inheritance of classes. For more info, visit https://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html – hfontanez Feb 04 '22 at 01:22
  • I also suggest for `Pet` to be an interface. The reason behind it is that even among the same class family, you could have dogs that are not pets. For example, wild dogs are not pets, but domesticated dogs could be. In your case, `OrganicCat` could be a `Pet`, but `RoboticCat` might not. However, both may inherit from the same parent (i.e. `AbstractCat`) – hfontanez Feb 04 '22 at 01:26
  • @frozenyogurtexpert, was your question answered? – hfontanez Feb 05 '22 at 15:05

1 Answers1

3

You could come up with something like this enter image description here

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.

enter image description here

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.

hfontanez
  • 5,774
  • 2
  • 25
  • 37
  • 1
    A very nice example of the Dependency inversion principle in action, good job. – Alexander Ivanchenko Feb 12 '22 at 19:42
  • Thank you, @AlexanderIvanchenko. When I saw this post, I immediately saw the "class explosion" problem and decided to break down the details (with UML) to introduce Dependency Injection which is the 100% correct way to address this issue. – hfontanez Feb 12 '22 at 19:55
  • 1
    @AlexanderIvanchenko I will take a look. – hfontanez Mar 17 '22 at 15:12
  • 1
    @AlexanderIvanchenko here's a link on a question I answered with regards to [Trie implementations](https://stackoverflow.com/a/71339760/2851311) just in case it could help. I think your answer is correct by the way. – hfontanez Mar 17 '22 at 16:04
  • 1
    @AlexanderIvanchenko Depends. As long as it is framed in the right context, I don't think they differ much. That said, clarity is seldom a bad thing. After all, the answer you provided doesn't lack it for a reason, I assume. Right? – hfontanez Mar 17 '22 at 16:31
  • 1
    Thanks, I owe you! I almost drown in these comments. – Alexander Ivanchenko Mar 17 '22 at 16:58
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/243037/discussion-between-hfontanez-and-alexander-ivanchenko). – hfontanez Mar 17 '22 at 17:49