Interfaces are a common tool in the Object-Oriented Programming tool belt, but they bring a lot of unnecessary rigidity to our programs. Recently, I stumbled upon an area of the Java API which demonstrates a more flexible designing using optional methods. I’d like to talk about it!
Table of Contents
Today, I wanted to talk a little bit about software design, specifically the concept of interfaces. Of course, as you’re probably aware at this point, I like to give a little bit of background behind the topic of the week. In this case, I got the idea during my training to teach a software course at my university. It’s a sequel to a course I already teach, where we cover rigid design principles tied to design by contract.
Now often times in the course I currently teach, students will ask why the design principles are so strict, and I usually give a variety of answers. For example:
- It’s good to learn some set of rules before you learn how to break them.
- It’s good to get used to following a shared set of principles or guidelines, as employers will often have their own.
- I may not be there for the sequel course, where an instructor may be more strict.
- Sometimes you just have to get through a course.
Each of these answers appeals to a different “why”, and not all students are going to appreciate all of them. Ultimately, I understand the frustration that students feel, especially when the principles are tied to grades. As a result, I try to weigh “correctness” more heavily (i.e., does the program work as intended?), while also promoting the idea that there is no perfect way to write and organize code but there are “better” ways than others. This can ease tensions as students learn to separate their development as coders from their grades.
Even better is that I’m finding that the sequel course leans less heavily on these strict rules and allows for a broader diversity of software development styles, paradigms, and principles. Overall, I think this is a good thing for the discipline, and it bodes well for me as an incoming instructor for the course.
The first event that tipped me off on this shift in perspective was while I was working through one of the homework assignments. It asks the students to look over the List interface of Java and compare how it’s designed to the way we design interfaces in our course. This got me thinking very heavily about how well that interface is designed, and so today I want to talk about it.
The Rigidity of Java Interfaces
In Java (and probably programming languages broadly), interfaces are classes that cannot be instantiated. Instead, they provide a list of method headers, which are expected to be implemented by any classes inheriting the interface.
In general, interfaces are used when we want to have some shared set of methods across a variety of classes. For example, many popular programming languages have interfaces for iterating over a collection, often called iterable. The iterable interface usually contains a handful of methods which can be used to iterator over a collection without knowing its underlying structure. As a result, language syntax, like a for each loop, can be designed to iterate over any data structure implementing iterable.
Alternatively, we might use an interface when we want to design a hierarchy, where the items near the top of the hierarchy should not be instantiated. I like to think about this in the way we do taxonomy for life on Earth. At the top, we would have an interface for life, which might define some key methods (e.g., isAlive()). Under that, we might define interfaces for all of the Kingdoms of life (e.g., animalia, plantae, etc.). Only once you reach the leaves of the taxonomy would you actually create any classes (e.g., felis catus). These classes would then inherit every aspect of the interfaces above them.
Now, if you know anything about interfaces in Java, they are very rigid. Whatever methods you provide in them, they must be implemented in the subclass. So, in the biology example, a cat would inherit everything from its taxonomy: animalia, chordata, mammalia, carnivora, feliformia, felidae, felinae, felis, and catus. At a glance, this might make sense. In reality, however, the hierarchy is just too strict. There are going to be features generally associated with parts of the taxonomy that don’t apply to the particular species. Just look at the platypus!
To simply separate the orders, mammals are warm-blooded, give birth to live young and feed them milk. Birds are also warm-blooded but lay eggs, and reptiles are cold-blooded egg-layers that rely on the Sun or another heat source to warm them up.The Natural History Museum
In other words, if we were to apply the idea that all mammals give birth to live young to our interface, it would be inherited by the Platypus. For example, I am imagining a “birth” method passing down to the Platypus. What would that method do? Simultaneously, there would be a “lay egg(s)” method that would be inherited further down the hierarchy. Having both seems kind of strange. Surely, the classification system itself is too rigid, as others have pointed out:
Indeed, it should have been. the platypus shows the limits of the value of our classification systems. We are trying to impose rigid structure on something much more complex. (This is quite a social problem too but I’ll leave that for another day).Neil Bendle
So, what do we do?
Analyzing the List Interface of Java
In our courses, we teach the idea that classes should have only the methods of any inherited interfaces and that those methods must all be implemented exactly according to their contracts. In general, this is fine and affords a lot of perks. For example, we can easily swap out implementations because we know they will all behave identically. I think the downside is best conveyed by the List interface of Java.
If you haven’t already, I’d recommend taking a look at the List interface. It’s pretty cool. It basically defines all of the behaviors that you’d expect from a list (e.g., the ability to get its size, add and remove elements, and sort the elements). However, it does something that is very different from the way we tend to teach interfaces: it defines optional methods.
Again, a class must always implement all of the methods of its interfaces. However, by documenting which methods are optional in the interface, you are giving users the ability to raise exceptions in place of implementation. The distinction to me is really clever. You’re basically saying that it’s okay not to implement some of the methods, if they’re not needed.
The example that immediately comes to mind for me is mutability. Maybe you want to implement an immutable list. A list interface that forces you to implement add and remove is going to violate your constraint of immutability. Instead, for any method that modifies the list, you can just throw an error.
The alternative would be to provide a variety of interfaces for all the possible combinations of behaviors that you might want for a list: MutableList, AddOnlyList, SortOnlyList, etc. Or, you might try to create some sort of hierarchy of list interfaces, similar to the way we organize life on Earth. Maybe there is some general List interface at the top that is read-only. Then, there are subinterfaces that provide more granular support.
In general, I think the approach that the Java folks took is much better. It might be a little more annoying to the user because they can’t just let their IDEs autocomplete methods, but I think getting in the habit of reading documentation would be a positive shift in our discipline. Also, it brings a host of additional benefits that interfaces bring regardless. Specifically, we’re able to build out a host of utility functions that will work for any implementation (see Collections). To me, the layering is a huge benefit, while also gaining the benefit of flexibility.
With all that said, this was more free form than my usual style of article. I’ve been very busy with life lately, so I haven’t had a chance to write anything more thought out. Of course, I’m sure some of you will enjoy my rambling. If so, there’s always more where that came from:
- Design by Contract Hinges on Implication
- Understanding Short-Circuit Evaluation in Software Design
- There Has to Be a Better Way: Reflecting on My Automation Catchphrase
And as always, you’re welcome to take your support a step further. Otherwise, have a good one! See you soon.
I'm embracing the virtual world and moving all of my computer science exams online. Here's why!
Amid all the chaos of my daily TODO list, I was able to find order by sorting my tasks by my values. Maybe you'll find some "value" in this tip as well.