user4617449
user4617449

Reputation:

Java class inheriting from multiple interfaces without code duplication

Imagine 5 interfaces:

  1. IAnimal
  2. ICanivore extends IAnimal
  3. IPet extends IAnimal
  4. IDog extends IPet, ICarnivore
  5. ICat extends IPet, ICarnivore

I now want to create 2 new classes for dogs and cats, class MyDog and class MyCat. If I simply do class MyDog implements IDog and class MyCat implements ICat, then I will have a lot of code duplication between the two animals, which is unnecessary since the implementations for IAnimal, ICarnivore etc. will be identical between the two animals.

Is there a way to implement the methods of every interface only once?

PS: Interfaces and dependecies/hierarchies may not be changed.

Upvotes: 0

Views: 123

Answers (1)

Edwin Buck
Edwin Buck

Reputation: 70909

Let's clarify about the code duplication, to make certain we are talking about the same things, and why code duplication is bad:

  1. Code duplication in the interfaces.

Both of these classes will use a number of similar interfaces. The interface is written once and used many times. This is desirable sometimes; because, modifying the interface triggers modifications in all the child classes. This is also sometimes undesirable, as a modification might mean adding code to classes that you would rather not modify (but have to be modified due to the interface change being needed by an unrelated class).

  1. Code duplication in the implementation.

Both of these classes will implement a number of similar methods. The implementation of specific methods is often written many times. This is typically undesirable due to the need for software maintenance. If a bug is found in one implementation of a commonly copied method, one would need to implement the fix in every copy, where missed copies would keep the bug in the code longer than desired. On the other hand, if these copies are meant to have different lifecycles, then one might actually desire this duplication, as a fix to one module (a larger collection of code with its own update schedule) wouldn't mandate releasing all modules (where the bug may have little or no impact).

So Code Duplication is generally bad; but, it is not always bad. The scenarios where it is bad are far greater than those scenarios where it makes sense.

To remove code duplication, the traditional Java approach is to use abstract classes. You find the duplicates that are in common, and create an abstract class. The naming convention is often 'AbstractDog' or 'DefaultDog'; both of which are pretty bad naming conventions (When you finally do the deep dive into naming) This abstract class will have the common implementations for all of the methods that it's category should use.

In your case, 'AbstractPet' might be a choice or 'AbstractMammal'. Note that 'Mammal' is already an abstract idea, so maybe let's just drop the redundant 'Abstract'

public abstract class Mammal extends IAnimal {

    private float heartbeatsPerMinute;

    public Mammal(float heartbeatsPerMinute) {
        this.heartbeatsPerMinute = heartbeatsPerMinute;
    }

    public float getPulse() {
        return heartbeatsPerMinute;
    }

    public abstract getCommonName();

}

Now all of your mammals don't need to implement getHeartbeatsPerMinute() and that may (or may not) be one of the interface's required methods.

The main problem with this kind of modeling isn't the language. The main problem is that people often under-evaulate the classes, abstract classes, and interfaces and come up with combinations that fail to subdivide into mathematical Sets (I'm not talking the Java Sets, but the discrete math "Set theory" rules).

It's easy to get started with the "Class / Abstract Class / Interface" approach; but, if you choose an abstract method that isn't really a common denominator across it's intended domain, you have to "override" it for some of the domain. This means you have a Set that both "includes" some behavior and at the same time doesn't. This kind of unclear thinking leads to code that becomes hard to extend, maintain, and reason about over time; because, you keep collecting "exception cases".

One exception case might be easy to deal with, but combining two exception cases, typically means evaluating 4 scenrios (both present, both absent, and two where one other is present). Adding in another exception case multiplies the scenarios currently in existance by two. Quickly one can see that six exceptions will lead to 64 scenarios, of which you will never really test them all for correct code functionality.

So, go ahead and experiment with abstract classes; but, be aware that each class acts like a set of it's instances. Also be aware that unlike real set theory, Java (and other languages) don't have a "not in this set" expression. Be aware that while you can combine Interfaces freely, you can't combine abstract classes (this limits the kinds of divisions of types to a subset of set theory, which is easy to implement (and sufficient for most problems). And keep in mind that it is both possible to design a (set) class hierarchy that fails to divide up the concepts according to the real worls in addition to designing a class hierarcy that fails to compile within the scope of Java's rules.

In practice it really isn't hard to use classes / abstract classes / interfaces; however, if you aren't given a glimpse of what you need to do to make your programs easily maintainable with these tools, you programs (and you) will probably suffer from self-inflicted harm. If your simple approaches stop working one day, break out some paper and start drawing Venn diagrams. In a few minutes you'll probably realize the flaws in the program's model in a way that tells you what you need (the rest of the trick is getting the rest of the program into that state).

Good luck!

PS. Sorry about the deviation from the code duplication bits. Basically, we all accept that there are some times when you can't make Java's 'abstract class' hierarcy collapse to a chain that is easily usable in ever scenario. When that happens, code duplication will occur. That's a weakness of the Java language; but, every programming language has weaknesses. Sometime those weaknesses aren't bad enough to even matter, sometimes they are irrelevant to the problem being solved, and sometimes you should switch programming languages.

The decision to switch has to do with accepting that this problem needs features where a weakness directly hurts, but keep in mind that with the switch, you get an entirely different set of weaknesses, often overlapping where teh previous language had strengths.

Upvotes: 1

Related Questions