Introduction
I have read multiple posts about implementing interfaces and abstract classes here on SO. I have found one in particular that I would like to link here - Link - Interface with default methods vs abstract class, it covers the same question. As the accepted answer, it is recommended to use the default methods of interfaces when it is possible to do this. But the comment below this answer stating "this feature feels more like a hack to me" explains my problem.
Default methods have been introduced to make implementations of interfaces more flexible - when an interface is changed it is not necessarily required in the implementing classes to (re)write code. Therefore, using a default method of an interface just to realize a method in all implementing classes - quote: "feels more like a hack to me".
Example for my examination:
Classes overview:
- Item - Abstract Superclass of all Items
- Water - Item which is consumable
- Stone - Item which is not consumable
- Consumable - Interface with some methods for consumable items (those methods have to be overriden by all implementing classes)
Combining those:
Water is an Item and implements Consumable; Stone is also an Item and does not implement Consumable.
My examination
I would like to implement a method which all Items have to implement. Therefore, I declare the signature in the class Item.
protected abstract boolean isConsumable();
//return true if class implements (or rather "is consumable") Consumable and false in case it does not
Quick edit: I am aware that instanceof might solve this particular example - if possible think of a more complicated example that makes it necessary to implement the method in the first place. (Thanks to Sp00m and Eugene)
Now I have several options:
- Implement the method by hand in every single subclass of Item (this is definitely not possible when scaling the application).
As mentioned above when scaling the application this would be impractical or highly inefficient.
- Implementing the method inside of the interface as a default method so the Consumable classes already implement the method which is required by the superclass Item.
This is the solution recommended by the other post - I see the advantages of implementing it in this way:
Quote - "The good thing about this new feature is that, where before you were forced to use an abstract class for the convenience methods, thus constraining the implementor to single inheritance, now you can have a really clean design with just the interface and a minimum of implementation effort forced on the programmer." Link
But in my opinion, it still seems contradictory to the original idea of default methods which I mentioned in my introduction. Furthermore, when scaling the application and introducing more methods that share the same implementation for all Consumables (as the example method isConsumable()
), the interface would implement several default methods which contradicts the idea of an interface not implementing the actual method.
- Introducing sub-superclasses instead of an interface - for example the class Consumable as an abstract subclass of Item and superclass of Water.
It offers the opportunity to write the default case for a method in Item (example: isConsumable() //return false
) and afterwards override this in the sub-superclass. The problem that occurs here: When scaling the application and introducing more sub-superclasses (as the Consumable class), the actual Items would start to extend more than one sub-superclass. It might not be a bad thing because it is necessary to do the same with interfaces too but it makes the inheritance tree complicated - Example: An item might now extend a subsuperclass ALayer2 which is a sub-superclass of ALayer1 which extends Item (layer0).
- Introducing another superclass (thus same layer as Item) - for example the class Consumable as an abstract class which will be another superclass of Water. That means that Water would have to extend Item & Consumable
This option offers flexibility. It is possible to create a whole new inheritance tree for the new superclass while still being able to see the actual inheritance of Item. But the downside I discovered is the implementation of this structure in the actual classes and using those later on - Example: How would I be able to say: A Consumable is an Item when Consumable would be able to have subclasses that are not meant for Items. The whole converting process will possibly cause a headache - more likely than the structure of Option 3.
Question
What would be the right option to implement this structure?
- Is it one of my listed options?
- Is it a variation of those?
- Or is it another option I have not thought about, yet?
I have chosen a very simple example - please keep scalability for future implementations in mind when answering. Thanks for any help in advance.
Edit#1
Java does not allow multiple inheritance. This will effect the option 4. Using multiple interfaces (because you can implement more than one) might be a workaround, unfortunately the default-method will be necessary again which is exactly the kind of implementation I have been trying to avoid originally. Link - Multiple inheritance problem with possible solution
Item
is aware of whether its instances areConsumable
or not. I would personally rather not have such a method, and usetheInstance instanceof Consumable
when needed. – KendreItem::isConsumable
and a separate interfaceConsumable
? why doesItem
has to know thatConsumable
even exists? – Allongeextends
exactly one other class. But Java does have multiple inheritance: it is the result of classes implementing interfaces. Interfaces could not otherwise serve their purpose -- implementations could not be used polymorphically. With the introduction of default methods for interfaces, even the "of implementation" part is blurred. – Johnsiejohnsondefault
method to implement anabstract
method of the super class. SinceItem
does not implementConsumable
, there is no relationship between adefault
method inConsumable
and a declared method ofItem
with the same signature. So you have two unrelated methods. The Java designers solved this potential error source, by letting the non-interface class’ method always win, even if it isabstract
. That’s also compatible with pre-Java 8 code. – Subbaseclass A { void foo() {} }
andclass B { void foo() {} }
. Of course, without multiple inheritance, there’s no problem with them. So there are only scenarios involvingdefault
methods, where you may encounter such methods in one class, either, by inheriting them from two unrelated interfaces (then, you get an error unless you override them), or by inheriting one from an interface and one from the super class (then, you get the super class method). – Subbase