Assignment: Loop on a given List of strings and print each member with the following rules:
- the first value must be printed as it is
- other elements must be printed in uppercase
- at the end of the loop print out “Exit”
A first version
public class Service {
public void loop(List strings) {
int index = 0;
for (String string : strings) {
if (index == 0) {
System.out.println(string);
} else {
System.out.println(string.toUpperCase());
}
index++;
}
System.out.println("Exit");
}
}
Ok, working, brief and clear; let’s try to “complicate” things a bit;
State Pattern
The tasks executed at each iteration in the example are trivial, but in a real scenario it could be complicated: why don’t we isolate those tasks in a separate method or even a separate class?
Let’s introduce an Iteration interface that represents the task executed on a given iteration.
interface Iteration {
void execute(String value);
}
three tasks, three initial implementations:
Iteration firstIteration = new Iteration() {
public void execute(String value) {
System.out.println(value);
}
}
Iteration middleIteration = new Iteration() {
public void execute(String value) {
System.out.println(value.toUppercase());
}
}
private final Iteration noIteration = new Iteration() {
public void execute() {
System.out.println("Exit");
}
}
Now, each task should be responsible to execute its logic and in addition it should provide some mechanism to move forward on the next iteration.
One possible way to do that is to introduce a small change in the Iteration interface and in the outer class which manages the tasks:
interface Iteration {
void execute();
}
public class Loop {
private final Iterator iterator;
...
public Loop(final List strings) {
iterator = strings.iterator();
}
Iteration firstIteration = new Iteration() {
public void execute() {
System.out.println(iterator.next());
}
}
Iteration middleIteration = new Iteration() {
public void execute() {
System.out.println(iterator.next().toUppercase());
}
}
Iteration lastIteration = new Iteration() {
public void execute() {
System.out.println("Exit");
}
}
}
At this point we need to maintain a reference to the current iteration. Let’s introduce another instance member : a currentIteration which is initially set to firstIteration
private Iteration currentIteration = firstIteration;
Two things are still missing. First, the Loop class needs to have a method for starting the loop execution:
public void start() {
currentIteration.execute();
}
and then, each task has to decide how to move forward on the next iteration. That logic can be isolated in a composed method:
private void nextIteration() {
currentIteration = iterator.hasNext()
? middleIteration
: noIteration;
currentIteration.execute();
}
I know: this is still a conditional logic; however, it is not exactly the same as the first Loop version: there are no indexes neither temporary variables and each Iteration class contains exactly what needs to be done at a given iteration cycle.
The code in this example is just an exercise, and I agree with you the benefits are not so evident; this because the underlying problem is very simple.
This is the final version of the Loop class:
interface Iteration {
void execute();
}
public class Loop {
Iteration firstIteration = new Iteration() {
public void execute() {
System.out.println(iterator.next());
nextIteration();
}
}
Iteration middleIteration = new Iteration() {
public void execute() {
System.out.println(iterator.next().toUppercase());
nextIteration();
}
}
Iteration lastIteration = new Iteration() {
public void execute() {
System.out.println("Exit");
}
}
private final Iterator iterator;
private Iteration currentIteration = firstIteration;
public Loop(final List strings) {
iterator = strings.iterator();
}
public void start() {
currentIteration.execute();
}
private void nextIteration() {
currentIteration = iterator.hasNext()
? middleIteration
: noIteration;
currentIteration.execute();
}
}