Diciotto
Decorator Pattern
At the end of yesterday’s post I hinted that my DelayedComputer was tied to the DumbComputer and that it might be necessary to also delay the PerfectComputer. That turned out to be the case almost immediately.
The first solution that came to mind was to create a DelayedPerfectComputer, that simply extended the PerfectComputer and overrode the choosePosition method. However, that would mean I’d end up with two new classes that both extended different classes in exactly the same way. What if I needed to create yet another computer player, for example the CheatingComputer. If that also need to be delayed I’d end up creating yet another delay class.
How is it possible to have a new class represent two other classes but with extra features?
Original Delayed Computer
public class DelayedComputer extends DumbComputer {
private int delay;
public DelayedComputer(Mark mark, int delay) {
super(mark);
this.delay = delay;
}
@Override
public int choosePosition(UserInterface ui, Board board) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return super.choosePosition(ui, board);
}
}
So there are a couple of issues with this, firstly it is coupled specificially to the DumbComputer class, but also in order to test this I have to pass a delay figure in of 0 so that the test suite isn’t increased in length.
So, the first part of the solution is to convert the DelayedComputer player into a decorator and then deal with the issue of delays.
Decorator Delayed Computer
public class DelayedComputer implements Player {
private int delay;
private Player player;
public DelayedComputer(Player player, int delay) {
this.player = player;
this.delay = delay;
}
@Override
public int choosePosition(UserInterface ui, Board board) {
try {
Thread.sleep(delay);
ui.displayComputerThinking();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return player.choosePosition(ui, board);
}
public Mark getMark() {
return player.getMark();
}
}
So now, anytype of Player including Human, can have a delay added to their choosePosition method. This is a much better implementation since it allows the delay to be added to both classes, without modifying those classes.
Finally, I needed to deal with the actual delay issue. This was solved through some Dependency Inversion.
Decorator Delayed Computer
public class DelayedComputer implements Player {
private int delay;
private Delayer delayer;
private Player player;
public DelayedComputer(Player player, Delayer delayer, int delay) {
this.player = player;
this.delayer = delayer;
this.delay = delay;
}
@Override
public int choosePosition(UserInterface ui, Board board) {
delayer.sleep(delay);
return player.choosePosition(ui, board);
}
public Mark getMark() {
return player.getMark();
}
}
Delayer Interface
public interface Delayer {
void sleep(int delay);
}
ThreadDelayer
public class ThreadDelayer implements Delayer {
@Override
public void sleep(int delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
MockDelayer
public class MockDelayer implements Delayer {
public boolean hasSleepBeenCalled;
public MockDelayer() {
this.hasSleepBeenCalled = false;
}
@Override
public void sleep(int delay) {
hasSleepBeenCalled = true;
}
}
A nicely decoupled solution with some flexibility for later. Should it be required, I is not too much work to create an abstract PlayerDecorator class that would allow other additions to their behaviour as appropriate. For now though, the delay is sufficient.