Implementing methods using default methods of interfaces - Contradictory?

CrimsonMike :

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:

  1. 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.

  1. 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.

  1. 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).

  1. 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

Roland :

I am missing option 5 (or maybe I didn't read correctly): supply the method inside the Item itself.

Assuming that consumable items are identifiable via the Consumable-interface here are the reasons why I can not recommend most of the points you listed: The first one (i.e. implement it in every subclass) is just too much for something as simple as this instanceof Consumable. The second might be ok, but wouldn't be my first choice. The third and the fourth I can't recommend at all. If I can just give one advice then it's probably to think about inheritance twice and to never ever use intermediate classes just because they made your life easier at one point in time. Probably this will hurt you in future when your class hierarchy becomes more complex (note: I do not say that you shouldn't use intermediate classes at all ;-)).

So what I would do for this specific case? I would rather implement something like the following in the abstract Item class:

public final boolean isConsumable() {
  return this instanceof Consumable;
}

But maybe I wouldn't even supply such a method as it is as good as writing item instanceof Consumable in the first place.

When would I use the default methods of interfaces instead? Maybe when the interface has rather a mixin character or when the implementation makes more sense for the interface then the abstract class, e.g. a specific function of the Consumable I would probably supply as default method there and not in any pseudo-implementing class just so that other classes can then again extend from it... I also really like the following answer (or rather the quote) regarding mixin.

Regarding your edit: "Java does not allow multiple inheritance" ... well, with the mixins something similar as multiple inheritance is possible. You can implement many interfaces and the interfaces themselves can extend also many others. With the default methods you have something reusable in place then :-)

So, why are default methods in interfaces ok to use (or not contradicting the interface definition itself):

  • to supply a simple or naive implementation that already suffices the most use cases (where implementing classes may deliver specific, specialized and/or optimized functionality)
  • when it is clear from all the parameters and the context what the method has to do (and there is no suitable abstract class in place)
  • for template methods, i.e. when they issue calls to abstract methods to perform some work whose scope is broader. Typical example would be Iterable.forEach, which uses the abstract method iterator() of Iterable and applies the provided action to each one of its elements.

Thanks Federico Peralta Schaffner for the suggestions.

Backward compatibility is here for completeness too, but listed seperately as are the functional interfaces: The default implementation also helps to not break existing code, when adding new functions (either by just throwing an exception so that the code still keeps compiling or by supplying an appropriate implementation that works for all implementing classes). For functional interfaces, which is rather a special interface case, the default methods are rather crucial. Functional interfaces can easily be enhanced with functionality, which itself doesn't need any specific implementation. Just consider Predicate as an example.. you supply test, but you get also negate, or and and in addition (supplied as default methods). Lots of functional interfaces supply additional contextual functions via default methods.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=36438&siteId=1