What is Refactoring?

Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior. Its heart is a series of small behavior preserving transformations. Each transformation (called a ‘refactoring’) does little, but a sequence of transformations can produce a significant restructuring. Since each refactoring is small, it’s less likely to go wrong. The system is also kept fully working after each small refactoring, reducing the chances that a system can get seriously broken during the restructuring.

Just like Design Patterns, refactorings have names so we can communicate about them more effectively.

Remember that refactoring is about "altering its internal structure without changing its external behavior" so if all tests pass before the refactoring, all tests must also pass after, or its not refactoring, it’s something else.

First, your RockPaper and Scissors classes have a lot of similarities: they have so much in common, both in code (identical methods) and in terms of business logic (they are all gestures, according to the Wikipedia entry on Rock Paper Scissors) so we want to perform an Extract Superclass refactoring on them.

In the Eclipse Package Explorer, right-click on one of those classes (I right clicked on Rock) and select Refactor > Extract Superclass:

Click the image to enlarge

In the new window, under &uuot;Specify actions for members" select the three beats(…) methods, and Set the Action as "declare abstract in superclass". This means we’re creating an abstract superclass, which will declare those methods.

Then, under "Types to extract the superclass from:" click on "Add" and add the other classes (Rock, Paper and Scissors) to the list.

Do not check "Use the extracted class where possible". If it is checked, un-check it.

Click the image to enlarge

Specify a name for the new superclass, Gesture and click "Finish".

You’ll see a new class created, which Rock, Paper and Scissors all extend.

Run all tests; they should still pass. We’ll now continue with development.

TDD Step 5.1 – Write a Test

Write a new TestCase; call it TestPlayer and give it one method, testThrowRock:

Class Player now has three similar methods – throwRock, throwPaper and throwScissors – which return the same type.

Having different methods like this doesn’t lend itself to scaling the system; if we (later) are asked to add Lizard and Spock to the playable gestures it will require that we add new methods to class Player – throwLizard() and throwSpock().

Instead, this should be one parameterized method, so we’re going to apply the Parameterize Method refactoring to address the issue.

The bad news is Eclipse won’t do this one for you; you’ll have to do it step by step.

First, change the name of one of the methods to throwGesture by right clicking on the method name, and selecting Refactor > Rename.

Click the image to enlarge

Then change the method signature; we need to add a String parameter.

Click the image to enlarge

Call it gestureName and make its default value the name of the gesture, with quotations – so when refactoring method throwRock the default value will be "Rock", when refactoring method throwPaper the default value will be "Paper" and when refactoring method throwScissors the default value will be "Scissors".

If you get an error message about Duplicate method throwGesture(String) in Player, click ‘Continue’ and don’t worry – it’s a good sign that you’re doing everything right.

Once you’ve done this to all three methods, class Player should have a bunch of build errors and look like this:

Whether this is still unit testing is something the academics might argue over – since we have two players – but the focus of this tutorial is TDD, not unit testing. So it’s a test and that’s good enough for now.

This tests won’t compile though: Eclipse will tell you that The method beats(Rock) in the type Gesture is not applicable for the arguments (Gesture).

TDD Step 7.2 – Run all tests and see if the new one fails

Since the tests won’t compile, we can’t run it and it fails implicitly.

TDD Step 7.3 – Write some code to make the test pass

Our variables paper and rock are declared of type Gesture, so they can’t be passed to any of the methods in class Gesture – which expects a Rock, Paper or Scissors object.

One student suggested adding a beats(Gesture) method to class Gesture, like so:

The following is a fail:

public int beats(Gesture g) {
throw new RuntimeException("I didn't think this would ever get called!");
}

… suggesting that – since the runtime type of a Gesture will always be Rock, Paper or Scissors, the method beats(Gesture) will never get called and the compiler will be happy.

This is not the case though. If you add that method, your test fails.

The Problem: Overloading in Java and Compile-Time Binding

In Java, overriding is always bound at runtime, but overloading is a little more complicated: dynamic dispatching means that the implementation of a method will be chosen at runtime, but the method’s signature is nonetheless determined at compile time. (Don’t believe me? Check out the Java Language Specification, Chapter 8.4.9!)

You can understand the source of the problem better if we cast rock to Gesture like so:

… but since rock is declared of type Gesture this is superfluous! Either way – with or without a cast – so long as the declared type of rock is Gesture, the method with signature beats(Gesture) is called. Were Joshua Bloch explaining this, he would warn you that overloadings represent opportunities for error and confusion.

For more on the confusion of overloading, see the puzzler "Making a Hash of It" on page 137 of the book Java Puzzlers.

The Solution

The solution is to use a slightly confusing mix of method overloading and method overriding.

Add beats(Gesture) as an abstract method to class Gesture:

public abstract Gesture beats(Gesture gesture);

… and implement it in each subclass as follows:

public int beats(Gesture gesture) {
return gesture.beats(this);
}

The key is that the variable this has a declared type which is consistent with the runtime type. In class Rock the compiler sees: