4
\$\begingroup\$

I am trying to fully understand interfaces and abstract classes in Java and from what I understood, I created a sample console application. Is there an error in the design or is this basically what interfaces and abstract classes are?

Animals.java (interface):

interface Animals {

    void callSound();
    int run();

}

Feline.java (abstract class):

abstract class Feline implements Animals {

    @Override
    public void callSound() {
        System.out.println("roar");
    }

}

Canine.java (abstract class):

abstract class Canine implements Animals {

    @Override
    public void callSound() {
        System.out.println("howl");
    }

}

Lion.java (class):

class Lion extends Feline {

    @Override
    public void callSound() {
        super.callSound();
    }

    @Override
    public int run() {
        return 40;
    }

}

Cat.java (class):

class Cat extends Feline {

    @Override
    public void callSound() {
        System.out.println("meow");
    }

    @Override
    public int run() {
        return 30;
    }

}

Wolf.java (class):

class Wolf extends Canine {

    @Override
    public void callSound() {
        super.callSound();
    }

    @Override
    public int run() {
        return 20;
    }

}

Dog.java (class):

class Dog extends Canine {

    @Override
    public void callSound() {
        System.out.println("woof");
        super.callSound();
    }

    @Override
    public int run() {
        return 10;
    }

}

Main.java:

public class Main {

    public static void main(String[] args) {
        Animals[] animals = new Animals[4];
        animals[0] = new Cat();
        animals[1] = new Dog();
        animals[2] = new Wolf();
        animals[3] = new Lion();

        for (int i = 0; i < animals.length; i++) {
            animals[i].callSound();
        }
    }

}
\$\endgroup\$
  • \$\begingroup\$ The way you have it is okay (give or take), although I feel that animal examples aren't that great for learning OOP; this way of looking at it can lead to confusion and bad practice (such as preferring inheritance over composition) once we step out of the zone of trivial examples. For starters, please read up about the "square-rectangle problem" blackwasp.co.uk/squarerectangle.aspx (also known as "circle-ellipse"). It will enhance your understanding of inheritance and its limitations / caveats \$\endgroup\$ – Konrad Morawski Oct 18 '16 at 19:03
  • \$\begingroup\$ stackoverflow.com/questions/575217/… has some good insights (personally I agree with answers posted by Jon Skeet and jalf, not the accepted one, but all of it is good food for thought) \$\endgroup\$ – Konrad Morawski Oct 18 '16 at 19:06
5
\$\begingroup\$

Overall, it seems like your implementation is fine and accurately represents the relationships between the different classes and interface. You could improve on your design however if you would consider some style guides.

Explicitness

The default modifier for your classes / interfaces will make them only visible from within your package. While this may be what you intended, it's always better to be explicit and state the level of access you intend each portion of your code to have. This reduces guess-work made by others who work with your code (which can help to reduce bugs made based on assumptions) and it improves the readability.

This has the additional benefit of making the rest of your code more consistent (since you do not use default modifiers for your methods).

Naming

EDIT

In the following section I borrow wisdom from .NET Land. This would appear to be contrary to the style guide used by most JAVA developers. As illustrated in the links below:

That being said, it used to be convention to not have to dial an area code in the U.S. but after seeing the shortcomings, it was changed to always require area code. My point is that conventions often change (and usually for pragmatic reasons in the programming community) and as a result be open to other schools of thought. I think you'll find if you take both pieces of advice (creating interfaces based on attributes / behaviors) along with the "I" prefix, it makes the most sense. Ultimately this is a SMALL piece of the review and as with the rest of it, feel free to take or not take the input.

END EDIT

Typically (in .NET and Eclipse) interfaces begin with the letter "I" to denote this fact. So your interface Animals would be better named IAnimal (also without the plural unless you intend the interface to handle multiple animals).

Separation of Concerns

This is more of an esoteric critique, but generally speaking you'll want your interfaces to be well-segregated and more specific. The current Animals interface doesn't really communicate what the interface is used for. If I were making an application for example that only showed pictures of various animals, it is unclear whether or not I should use your interface on my new class. Rather, it is better to split interfaces (usually) based on the behavior they add. This forces them to be very targeted and succint, which in turn produces code that is more stable and typically easier to maintain since changes are very isolated.

For example, instead of:

interface Animals {

    void callSound();
    int run();

}

You might have:

interface IMakesSound {
    void callSound();
}

interface ICanMove {
    int move();
}

interface IWarmBlooded {
    int currentBodyTemperature();
    bool isOverheated();
}

abstract class Animal implements IMakesSound, ICanMove {
    abstract void callSound();
    abstract int move();
}

abstract class Mammal extends Animal implements IWarmBlooded {
    private int _bodyTemp;
    abstract void callSound(); // We don't know what ALL mammals sound like
    abstract int move(); // Some mammals run, others walk, some swim, and some do all of the above.
    public int currentBodyTemperature() {
        return _bodyTemp;
    }

    public bool isOverheated() {
        return _bodyTemp > 98; // This can be overridden based on the child class if needed
    }
}

// Other classes here that inherit either from `Animal` or `Mammal`.

In the above example, I have separated out your interface into two separate interfaces and an abstract base type that combines them. Using this structure, I can create something that makes a sound but isn't an animal (for example, a robot toy dog) that can still have all of the attributes of a "real" dog, but none of the inherited animal features.

This allows your code to be more flexible and more loosely coupled (which is the point of using an interface). By having code dependent on these loosely typed constructs, it allows for more flexibility.

For example:

public class Main {

    public static void main(String[] args) {
        Animals[] animals = new Animals[4];
        animals[0] = new Cat();
        animals[1] = new Dog();
        animals[2] = new Wolf();
        animals[3] = new Lion();

        for (int i = 0; i < animals.length; i++) {
            animals[i].move();
        }

        ShowPetSounds(animals);
    }

    public static void ShowPetSounds(IMakesSound[] petNoises) {
        for (int i = 0; i < petNoises.length; i++) {
            petNoises[i].callSound();
        }
    }

}

This allows us to write code that only knows about pet sounds without having to worry about what kind of animal it is (or indeed that it is an animal). This allows us to have imaginary pets like dragons (which we all love) or unicorns that would defy the "normal" inheritance hierarchy of an Animal type.

Ultimately both abstract classes and interfaces should be designed to be helpful abstractions, not abstraction for that sake of abstraction. When you do this, you will find that as your code-base grows over time, it will respond much better to additional requirements and changes in the system.

\$\endgroup\$
  • \$\begingroup\$ Are you sure about "I" prefix for interface names? I have not seen it done in JDK interfaces. It would seem like a C# practice. \$\endgroup\$ – coderodde Oct 18 '16 at 8:47
  • \$\begingroup\$ It's true the I is more of a .NET convention, but in this case it makes sense as well. I'll update my answer to reflect this information. \$\endgroup\$ – xDaevax Oct 18 '16 at 18:00
  • \$\begingroup\$ It's a .NET convention, atypical in Java, and - as much as I tend to prefer .NET conventions (being both a Java and a C# dev), I think Java is right this time. It's typical Hungarian convention. One shouldn't care if an entity is an interface or not. That's not the point of having an interface. If anything, programming to an interface should be encouraged, so it's not interfaces that should be made look odd! In any case, while you're right that conventions change, the long-term consensus in Java community runs to the contrary of what you advice the OP. \$\endgroup\$ – Konrad Morawski Oct 18 '16 at 18:54
  • \$\begingroup\$ It's still a good and thought through answer, I upvoted it, with just that reservation. \$\endgroup\$ – Konrad Morawski Oct 18 '16 at 18:57
  • \$\begingroup\$ Thanks for your feedback. As I explained in my edit, it is contrary to convention. Do you think my caveat in the edit to my answer is sufficient to identify where my opinion differs from that of the community? \$\endgroup\$ – xDaevax Oct 18 '16 at 19:00
1
\$\begingroup\$

Advice 1

I would rename Animals to its singular form Animal.

Advice 2

In the Main, you can write more succintly:

for (Animal animal : new Animal[]{ new Cat(), new Dog(), new Wolf(), new Lion()}) {
    animal.callSound();
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.