Win a copy of Kotlin in Action this week in the Kotlin forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic

Java 8 interfaces with default method implementations  RSS feed

 
Michael Swierczek
Ranch Hand
Posts: 125
1
Clojure Java Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Kishori Sharan,

Thanks for taking the time to answer questions, and for writing the books.

I realize that the first advantage of default method implementations in interfaces with Java 8 is the ability to add a method to an existing interface without modifying every existing class that implements that interface. That strikes me as useful. But I'm curious what you think of a possible design philosophy change in Java from making most interface methods contain default implementations, and override them only as necessary? This strikes me as a better way of getting code modularity and avoiding duplication than classic parent to child inheritance. I wonder whether that will become common in Java code going forward, and whether it is a good thing if it does. What do you think?

I'm also curious about what other programming languages you have used and what you think of them. I ask that because it helps me get some sense of your perspective as an author.

Again, thank you for your time.

 
Junilu Lacar
Sheriff
Posts: 11138
160
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Michael Swierczek wrote:I'm curious what you think of a possible design philosophy change in Java from making most interface methods contain default implementations, and override them only as necessary? This strikes me as a better way of getting code modularity and avoiding duplication than classic parent to child inheritance. I wonder whether that will become common in Java code going forward, and whether it is a good thing if it does.

I think that's a very slippery slope to even consider going near. Interfaces are intended to define intent and hide implementation. That's already a difficult thing for many programmers and would-be designers to get right today. Now you want to encourage adding default implementations for most interface methods and blur the line that separates intent and implementation even more? I can almost hear the many cries of despair and much gnashing of teeth in the future if people start doing this regularly.

I don't see how that's a better way of getting code modularity either. I can understand how it might be seen as a way to avoid duplication but the current technique of creating abstract or base implementation classes to do that works just as well. I don't see any reason to mess with what works just for the sake of using new language features. There's just not that much difference in clarity or elegance to compel me to start using default methods en masse. They are intended to make it easy to extend widely used and already established interfaces and that's exactly how i plan to use them.

I would be very careful about using any feature in a way other than that for which it was intended. Unconventional and improper use is often accompanied by unintended and unforseen consequences that are usually detrimental to the overall health of the software.
 
Michael Swierczek
Ranch Hand
Posts: 125
1
Clojure Java Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I disagree. Think of a large, complex application. Say you have a bunch of classes that, in one way or another, must each provide implementations to eight different interfaces. What are your design options?

1. Make the parent class or classes a monstrous mess, with implementations of all eight interfaces. GiantMotherParentClass. Make subclasses that override the parent. ( God Object design anti-pattern, https://en.wikipedia.org/wiki/God_object )

2. Make an interface or abstract parent class DoesEverything that has sixteen methods, Interface1 getInterface1(), void setInterface1(Interface1 interface1), Interfac2 getInterface2(), void setInterface2(Interface2 interface2), etc... then write the implementations of the eight interfaces you need, wire up your child classes of DoesEverything or implementations of DoesEverything manually or with dependency injection . That has a lot to recommend it and it's very flexible, but it also makes for a ton of boilerplate code in Java. If you have five different implementations of Interface1, they're either going to perpetuate the same code duplication you're trying to avoid in the first place, or else you have to in turn split interface1 into DoesEverythingInterface1 and a bunch of mini-classes to handle the tasks that are mixed and matched the same way that your master 'DoesEverything' class does. This is all classic and correct application of the Strategy Design Pattern, but it makes for huge amounts of code and a lot of the created code is just ceremony.

3. Make eight interfaces with default method implementations. For the actual classes you want to create, do Class Foo implements Interface1, Interface2, Interface3, Interface4... and only override the behavior you need to change. Simplest design, smallest amount of duplicate code, but you don't have massive amounts of code sitting in just one class or one class hierarchy like you do with GiantMotherParentClass.

 
Junilu Lacar
Sheriff
Posts: 11138
160
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Those are not your only options. My first reaction to seeing a class that must provide implementations for eight different interfaces is: "WTH are you doing trying to take on that much responsibility?" Next would be: "Haven't you ever heard of composition and delegation?" The last thing I would do would be to try to complicate things by introducing default methods for the eight interfaces. That just seems like a big mess that wants to blow up into an ever bigger mess.
 
Junilu Lacar
Sheriff
Posts: 11138
160
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Michael Swierczek wrote:Make eight interfaces with default method implementations. For the actual classes you want to create, do Class Foo implements Interface1, Interface2, Interface3, Interface4... and only override the behavior you need to change. Simplest design, smallest amount of duplicate code, but you don't have massive amounts of code sitting in just one class or one class hierarchy like you do with GiantMotherParentClass.

I said during an excruciatingly painful three-day "architecture workshop" this week: "Unless you can show me actual code and design, all this is moot." Would you care to provide a realistic concrete example of your scenario?
 
Michael Swierczek
Ranch Hand
Posts: 125
1
Clojure Java Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
More specific example - our top level abstract parent class has methods for getting and setting our application-specific replacements/enhancements to ye olde HttpServletRequest and HttpServletResponse. Child level 1 attaches the current user account information. Child level 2 attaches localization hooks and validation layer. Child level 3 attaches output format handlers (our reports have six different possible output formats the user can select) plus conversion of HTML form input parameters into corresponding domain objects. Child level 4 adds the ability to save and load an entered report configuration. Child level 5 adds the specific database query logic for the date range of data this particular report family can draw upon, and some report-family-wide shared aspects of the SQL query for that report. Child level 6 adds the specific report name and title-builder, and creates the query-handler object using the domain objects from the input handler and the report-family-wide aspects of the query from the respective parents, plus of course it can override aspects of any of the parents when necessary.

The query-handler top level abstract parent class has abstract methods for creating the SQL query, methods for executing the query, and methods for massaging the results (consolidating some rows, changing the sort based on things our SQL database can't do well with stored procedures, etc...). Child 1 attaches a horde of convenience methods for query-building and data massaging. Child 2 adds methods for setting the display order and formatting of output fields. Child 3 adds report-area-family-specific overrides of parents in children of level 2 and 3, and convenience methods used by that entire report family. Child level 4 is the actual query builder, which uses the domain-object to parameter name map from the previous family hierarchy, plus the user account settings (preferences and permissions) to build the report.

It all works quite well in practice. And now that I've been here for over a year, I can bounce around the class hierarchies involved like a monkey and get things done pretty quickly. But when you first get to the job, understanding a bug or feature request requires jumping up and down class hierarchies like crazy trying to follow how a method at the bottom level invokes a method from level 4 which invokes a method from level 2, and then next invokes a method from level 5 which invokes a method from level 3 which invokes a method from level 1, and so on.

(Edit) Now, it's possible that you can't really separate these parent classes into separate interfaces, with our without default method implementations. I'm not even too clear on how well Java 8 handles multiple interfaces with the same methods. But it seems to me that really we need the following six methods everywhere - get/set (enhanced) HttpServletRequest, get/set (enhanced) HttpServletResponse, get/set AccountSettings. Then we can have interface Validation, interface OutputHandlers, interface InputConversion, interface SaveAndLoad, interface FamilySpecific, and then interface BuildQuery. The first four interfaces could use default method implementations. The last two interfaces would always be fully abstract because there is no sensible default for the methods. Likewise the query-handler can probably be split into five interfaces, and the default methods for the first three will stand as-is for the majority of cases.

Again, Strategy Design Pattern would work but default method implementations would be even simpler. And I'm thinking of suggesting a refactor along those lines, one of those two ways - but we've got a hundred thousand or so lines of code built on the current model (out of a million line project) so even if we get the design correct (same features and fewer bugs while easier to read with far fewer lines of code), I don't think the change has a business case.
(End Edit)

The interface-with-optional-default-methods pattern is already supported by mixins in Ruby (and JRuby) and by traits in Scala, Kotlin, and Groovy. So some language designers thought it was a useful feature.
 
Junilu Lacar
Sheriff
Posts: 11138
160
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
That sounds like a horrific mess of a design that sprung out of a "just get it out the door, we'll fix things up later!" attitude. Most programmers, given enough time and motivation, can adapt and learn to live in a mess. They can even become comfortable enough in that mess to accept the "fact" that "this is just the way things are" and be content to swing around this kind of jungle. For some reason, I just don't have that gene in me. I see a mess and I say, "Eff that, I'm not working in this filth. Let's see what we can do to clean it up." (My wife often tells me that she wishes I would have that same attitude in the real world, too. ) I get a sense that you are looking longingly outside your cage and wishing for some relief from the constant but eventually-and-now-bearable pain. I don't think that interface default methods are the key to unlocking that doorway to better times for you.

I admit, I only read your scenario up to "Child Level 3" before my brain couldn't take it anymore - the images that your description brings are just all too familiar; these are things I have to deal with all the time. This does not change my opinion though: the problem is with the current design. My first thought was "Ever heard of Servlet Filters? Sounds like those are what you need here, instead of this freakin' custom-built Frankenstein labyrinth you have." I wouldn't even think about default interface methods until I've cleaned up this design.
 
Michael Swierczek
Ranch Hand
Posts: 125
1
Clojure Java Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The problem with doing work in the ServletFilters is that you have to stick all of your objects into the HttpServletRequest or HttpSession attributes, right? Then at the next ServletFilter step, or in your final Servlet class, you have to do lots of getAttribute() calls and then some instanceof checks on what you get. Am I missing something? Doing everything in one place has (many) headaches, but you keep your type safety everywhere.

 
Junilu Lacar
Sheriff
Posts: 11138
160
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
If you're not using some kind of MVC framework to make it unnecessary to call getAttribute(), then your problem goes even deeper that you first let on. The likes of Spring MVC and even the venerable Struts will let you abstract those calls away and deal directly with POJOs that are decoupled from the HttpServletRequest/HttpServletResponse objects. Is that application not doing that? How old is this thing anyway?
 
Michael Swierczek
Ranch Hand
Posts: 125
1
Clojure Java Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I am miscommunicating something. The problem is that many dozens of non-homogeneous pieces of state need to be passed around. If you use a POJO, you've got a colossal POJO and another form of the same monster object problem that we already have. If you partition pieces of the data into smaller groups, you either need to pass them around in an HttpServletRequest attribute map, or something similar, right? Then you have the type-casting / instanceof problems. Right?

We're using Stripes.



 
Junilu Lacar
Sheriff
Posts: 11138
160
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Michael Swierczek wrote:More specific example - our top level abstract parent class has methods for getting and setting our application-specific replacements/enhancements to ye olde HttpServletRequest and HttpServletResponse. Child level 1 attaches the current user account information. Child level 2 attaches localization hooks and validation layer. Child level 3 attaches output format handlers (our reports have six different possible output formats the user can select) plus conversion of HTML form input parameters into corresponding domain objects. Child level 4 adds the ability to save and load an entered report configuration. Child level 5 adds the specific database query logic for the date range of data this particular report family can draw upon, and some report-family-wide shared aspects of the SQL query for that report. Child level 6 adds the specific report name and title-builder, and creates the query-handler object using the domain objects from the input handler and the report-family-wide aspects of the query from the respective parents, plus of course it can override aspects of any of the parents when necessary.

Ok, I got as far as Child level 5 this time. This description still screams "Separation of Concerns, baby!" This is a basic design problem. I fear that adding default interface methods to this design mess will only make matters worse in the long run. I strongly advise against trying to play with that new, shiny toy in this cluttered space before you have made a good go at finding ways to decouple all these orthogonal responsibilities.
 
Junilu Lacar
Sheriff
Posts: 11138
160
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Michael Swierczek wrote:I am miscommunicating something. The problem is that many dozens of non-homogeneous pieces of state need to be passed around. If you use a POJO, you've got a colossal POJO and another form of the same monster object problem that we already have. If you partition pieces of the data into smaller groups, you either need to pass them around in an HttpServletRequest attribute map, or something similar, right? Then you have the type-casting / instanceof problems. Right?

We could very well be on different pages. That's why programmers should be looking at actual code when they talk because we mostly suck at describing our programs without code as the shared "picture". That said, however, the more you respond with these kinds of descriptions and questions, the more I'm convinced that you're basing your assumptions on flawed approaches to design but unless you can show some code, I'm afraid we'll just be talking in circles.
 
Michael Swierczek
Ranch Hand
Posts: 125
1
Clojure Java Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Fair enough. I can't release private code, so we'll have to leave it at that. Thanks for the discussion, I appreciate it and enjoyed it.
 
Junilu Lacar
Sheriff
Posts: 11138
160
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks for staying with me, too.

Too bad we couldn't dig deeper into your little jungle of a code base though. I always love a good challenge (from code, that is) and it never ceases to amaze me when I find that just when I think I've seen everything, something else comes along and proves me wrong. Good luck and keep swinging!
 
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!