I am using a composite pattern with multiple leaf node classes which have specialist operations and a visitor pattern to allow those operations to be performed. In this example I've left out all the obvious accept
methods for clarity.
interface Command {
public int getCost();
}
class SimpleCommand implements Command {
private int cost;
public int getCost() {
return cost;
}
}
class MultiCommand implements Command {
private Command subcommand;
private int repeated;
public int getCost() {
return repeated * subcommand.getCost();
}
public void decrement() {
if (repeated > 0)
repeated--;
}
}
class CommandList implements Command {
private List<Command> commands;
public int getCost() {
return commands.stream().mapToInt(Command::getCost).sum();
}
public void add(Command command) {
commands.add(command);
}
}
interface CommandVisitor {
default void visitSimpleCommand(SimpleCommandCommand command) { }
default void visitMultiCommand(MultiCommand multiCommand) { }
default void visitCommandList(CommandList commandList) { }
}
It's now possible to build visitors to perform operations such as decrement
. However I find it easier to create a general purpose visitor that streams objects of a certain class so that any operation can be performed on them:
class MultiCommandCollector implements CommandVisitor {
private final Stream.Builder<MultiCommand> streamBuilder = Stream.builder();
public static Stream<MultiCommand> streamFor(Command command) {
MultiCommandVisitor visitor = new MultiCommandVisitor();
command.accept(visitor);
return visitor.streamBuilder.build();
}
public void visitMultiCommand(MultiCommand multiCommand) {
builder.accept(multiCommand);
}
}
This is used as you would expect. For example:
MultiCommandCollector.streamFor(command).forEach(MultiCommand::decrement);
This has one significant limitation: it can't be used to alter the hierarchy as the stream is processed. For example, the following fails:
CommandListCollector.streamFor(commandList).forEach(cl -> cl.add(command));
I can't think of an alternative elegant design that would allow this.
My question is: is there a natural extension to this design to allow a general purpose visitor that can also alter the hierarchy? In other words, is there a way that the visitor can visit one member then refresh the hierarchy before visiting the next? Is this compatible with the use of streams?