Section 10.6 Refactoring: Extracting Interfaces
Of course we can create an interface directly. But oftentimes the interface naturally grows out of an existing system. We have build some functionality, and then we realize that some methods of a class should be extracted as an interface. That is when the Extract Interface refactoring can come in handy.
As an example, we will revisit our little grade-reporting program. Let’s recall how we left it in the OOP chapter:
- The
Grade
class represents the grade entities. It has methodsgetCredits
andgetPoints
. Perhaps it should have more, but that’s all we needed at the time. - The
GradeReader
class represents an object that reads grades from some input. We can create it from an actual input string, anInputStream
or aScanner
. And it basically provides two methods for us,hasMoreEntries()
andprocessNextGradeRow()
, which should actually better be calledgetNextEntry()
or something like that. We won’t change it just yet as I have other plans for it. - A
Summary
class is given aGradeReader
object and uses it in itsinvoke()
method to read all the grades it can provide and then produce a result. - A
Main
class is starting the "real" system by creating aGradeReader
, creating aSummary
introducing them to each other and then calling onSummary
to do the hard lifting.
So here is the thing: What does our
Summary
class really need to know about GradeReader
? It simply needs to know that it can ask it for more entries. So it needs to know that it has a hasMoreEntries()
method and a processNextGradeRow()
method. But it really doesn’t care how the GradeReader
goes about doing that. However, just by knowing that it is a GradeReader
class, it actually does know all that stuff! I couldn’t for example use some sort of ArrayList
instead. And that’s a pity, because it means I can’t test things very well: I can only test the Summary
class by going through a GradeReader
. We will return to this point in the next section.So this is our thought then, we would like an interface that I will call for now just
Producer
. Let’s talk about how to do that with the Extract Interface refactoring.Subsection 10.6.1 Mechanics of Extract Interface Refactoring
- Decide on the class from which you would like to extract an interface, and the specific methods you would like to have on that interface.
- Create a new interface containing those methods.
- Add
implements ...
to the class definition line. - You will need to make the methods from the interface public where they are implemented in the class.
- Look for places where the class is used as a type (parameter, local variable and field declarations), and see if you can, and also if you should, change the type to use this interface instead.
With an IDE, you simply trigger the Extract Interface refactoring option and specify the methods you would like to have extracted. We do this to the
GradeReader
class, and the two methods we wanted to use elsewhere, and end up with the Producer
Interface:public interface Producer {
boolean hasMoreEntries();
Grade processNextGradeRow();
}
More importantly, our
Summary
class has now changed, to use Producer
:private final Producer reader;
private int numCourses = 0;
private double totalPoints = 0.00;
public Summary(Producer reader) {
this.reader = reader;
}
Hm naming that variable
reader
seems a bit silly now, but let’s hold on to that, as I do one more change. Looking at my interface, there is nothing special about the Grade
values that my interface returns, it could be any type. What I mean by that is that as far as what this interface expresses, I would like it to instead express the idea of "returning elements of some type". I want to use a type parameter for that, like so:public interface Producer<T> {
boolean hasMoreEntries();
T processNextGradeRow();
}
I also need to find the various places where
Producer
was being used, and replace them with Producer<Grade>
But this all would allow me to use this
Producer
interface in all sorts of situations, with little change.What’s that? You are saying that this is really what the
Iterator
interface basically does already? Yep, you bet! So, hm, we should stop using this Producer
interface and start using Iterator
. It will just take some renaming, but here is a logical sequence of steps to follow:- Start by renaming the two methods, into
hasNext
andnext
. - Then add
Iterator<Grade>
to theGradeReader
class definition list of interfaces:GradeReader implements Producer<Grade>, Iterator<Grade>
- Now find usages of
Producer
and replace them withIterator
, and everything should keep working. - Remove the
Producer<Grade>
from the list of implemented interfacers inGradeReader
, then delete the interface file that is no longer used anywhere.
And that’s it! We are now using the established interface for accessing a list of items. Actually we could also consider using a
Stream
instead, we saw a bit of those earlier. Maybe we’ll come back to that.Don’t reinvent the wheel! Use existing interfaces and classes when possible.