Reducing Dependency with the Infer Type Refactoring for Eclipse

Consider the following sample code, adapted from [1]. It contains something that practically all programs have, although it’s often undesired: a strong coupling between classes, in this case caused by the dependency of class MovieLister on class ColonDelimitedMovieFinder. The dependency is established in three places:

  1. at line 2, where field finder is declared to be of type ColonDelimitedMovieFinder;
  2. at line 4, where a new finder object is created as an instance of ColonDelimitedMovieFinder; and
  3. at line 7, where findAll() is called on finder.

1     public class MovieLister {
2         private ColonDelimitedMovieFinder finder;

3         public MovieLister() {
4             finder = new ColonDelimitedMovieFinder("movies1.txt");
5         }

6         public Movie[] moviesDirectedBy(String arg) {
7             List allMovies = finder.findAll();
8             for (Iterator it = allMovies.iterator(); it.hasNext();) {
9                 Movie movie = (Movie) it.next();
10                if (!movie.getDirector().equals(arg)) it.remove();
11            }
12            return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
13        }
14    }

15    public class ColonDelimitedMovieFinder {
16        String filename;

17        public ColonDelimitedMovieFinder(String filename) {
18            this.filename = filename;
19        }

20        public List findAll() {
21            // TODO Auto-generated method stub
22            return null;
23        }
24    }


You can download a zip containing the .java files here.

A first step of reducing this dependency is to introduce a new interface that captures the use of finder objects in class MovieLister. Such an interface could be created with the Extract Interface refactoring, but today’s implementations of this refactoring (including that of Eclipse) require you, the programmer, to decide what belongs into that interface. For the given program this is no problem: a quick inspection of the code of class MovieLister shows that findAll() is the only method called on finder. What, however, if finder were assigned to other variables or passed on (as an actual parameter) to other classes? How would you know which other methods are called on finder objects in these new contexts? How would you decide then what to put into the interface? The answer requires the collection of all methods that are possibly called on any variable finder might eventually get assigned to, and thus a systematic analysis of the assignments (including parameter passes) in your program: it requires construction of the transitive closure of the use of finder under variable assignment. This is exactly what Infer Type does for you.

To get an impression of the workings of the refactoring, you best try it out. To invoke Infer Type in Eclipse, double click on a reference where it is declared (a declaration element, in the given case finder in line 2) in the editor pane and select Infer Type from the context menu, thereby starting the refactoring.

After the refactoring has finished its analysis, it displays the new type (interface or abstract class, depending on the particulars of the specific case) and its position in the type hierarchy in a small UML-style diagram.

The orange colour indicates that interface Finder is a new type that the refactoring will create for you.

In order to introduce the new type, you must select it. You can then inspect (but not change!) the members of the new type by clicking the small triangle left of “Show methods …”, giving you the contents of the interface:

In the boxes below, you can enter the new type’s name and package, as well as decide whether it is to be an interface or an abstract class. In our case, just leave it as it is and press [Finish]. (If you check "Open new type in editor", the newly introduced type will be shown to you in the editor.)

After it has finished, you will find that Infer Type

  1. has introduced the new interface Finder,
  2. lets ColonDelimitedMovieFinder implement Finder, and
  3. has replaced ColonDelimitedMovieFinder with Finder in the declaration of field finder.

This may appear not a big deal to you, but as you use Infer Type in your own programs, you will find that it can do remarkable jobs for you. For instance,

  1. if the original reference (the one on which you invoked Infer Type) gets assigned to other references on which additional methods are called, it will add these methods to the new type of the original reference;
  2. if these other references have the same type as the original reference, it will change their declarations to the new type as well;
  3. if these other references are declared with supertypes that are also supertypes of the new (inferred) type, Infer Type will insert a corresponding extends or implements clause in the new type;
  4. if these other references are declared with supertypes that are not also supertypes of the new (inferred) type, Infer Type will compute new supertypes for them and uses them instead; and
  5. if Infer Type encounters a downcast in an assignment chain, it will respect this a programmer’s choice and terminate the chain (alternative behaviours have been implemented, but tend to ignore the programmer’s intent).

A detailed description of the inner workings of Infer Type can be found in [2].

Returning to the original decoupling problem, you will have noticed that the dependency of MovieLister on ColonDelimitedMovieFinder still persists in line 4, where the constructor of ColonDelimitedMovieFinder is called. This dependency can be removed by application of a pattern named Dependency Injection in [1]; it involves replacing the constructor


3         public MovieLister() {
4             finder = new ColonDelimitedMovieFinder("movies1.txt");
5         }


with


3         public MovieLister(ColonDelimitedMovieFinder finder) {
4             this.finder = finder;
5         }


(so-called Constructor Injection [1]), as well as introduction of an Assembler class that creates instances of Finder and MovieLister and plugs them together (not shown). In Eclipse, you can refactor the constructor automatically, simply be applying the Introduce Parameter refactoring on new ColonDelimitedMovieFinder("movies1.txt") in line 4, as in

However, you might contend that all that is achieved by this is a shift of the dependency from the method body to the method header. Fortunately, this dependency can immediately be removed by applying Infer Type on the newly introduced parameter, in this case finder in line 3. This time, analysis of the declaration element results in the following being displayed:

The inferred type of finder, the interface Finder, already exists; it will automatically be chosen and no new type needs to be added. After pressing [Finish], the refactoring changes the method header to


3         public MovieLister(Finder finder) {
4             this.finder = finder;
5         }


so that the dependency is completely removed. Note that this second application of Infer Type resembles Eclipse’s Use Supertype Where Possible refactoring, only that the supertype is chosen automatically and the replacement is localized (to a single declaration element and the references it gets assigned to).

Now if you think that having to apply three refactorings is not much of a help for such a simple change, you’re probably right. The good thing is that with Infer Type, you can do it with less. To find out how, restore the original program, apply the Introduce Parameter refactoring on new ColonDelimitedMovieFinder("movies1.txt") first, and then Infer Type on the newly introduced parameter. Is that better?

 

[1]       http://www.martinfowler.com/articles/injection.html

[2]       “Decoupling Code with Inferred Interfaces” submitted to SAC 2006.


home

The IntoJ Team, 2005.