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?
In my prior experience, Visitor pattern is useful to either query or to recreate the hierarchy. The querying part is obvious - you would simply listen for specific types of sub-objects and then build the query result as it fits. The other question, changing the hierarchy, is more difficult.
It may really get hard to change the hierarchy while iterating through it. Therefore, I know of two useful techniques that work fine in practice.
The first algorithm is applicable when elements are mutable. The second algorithm is applicable when elements are immutable.
Hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With