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
Gradeclass represents the grade entities. It has methodsgetCreditsandgetPoints. Perhaps it should have more, but thatβs all we needed at the time. -
The
GradeReaderclass represents an object that reads grades from some input. We can create it from an actual input string, anInputStreamor 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
Summaryclass is given aGradeReaderobject and uses it in itsinvoke()method to read all the grades it can provide and then produce a result. -
A
Mainclass is starting the "real" system by creating aGradeReader, creating aSummaryintroducing them to each other and then calling onSummaryto 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();
}
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
hasNextandnext. -
Then add
Iterator<Grade>to theGradeReaderclass definition list of interfaces:GradeReader implements Producer<Grade>, Iterator<Grade> -
Now find usages of
Producerand 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.
