mardi 5 avril 2016

Using Visitor and Composite patterns to build a filtered stream

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?

Aucun commentaire:

Enregistrer un commentaire