Использование шаблонов Visitor и Composite для создания отфильтрованного потока

Я использую составной шаблон с несколькими классами листовых узлов, у которых есть специальные операции, и шаблон посетителя, позволяющий выполнять эти операции. В этом примере я опустил все очевидные accept методы для ясности.

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) { }
}

Теперь можно создавать посетителей для выполнения таких операций, как decrement. Однако мне проще создать посетителя общего назначения, который транслирует объекты определенного класса, чтобы с ними можно было выполнять любую операцию:

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);
    }
}

Это используется, как и следовало ожидать. Например:

MultiCommandCollector.streamFor(command).forEach(MultiCommand::decrement);

У этого есть одно существенное ограничение: его нельзя использовать для изменения иерархии во время обработки потока. Например, следующее не удается:

CommandListCollector.streamFor(commandList).forEach(cl -> cl.add(command));

Я не могу придумать альтернативный элегантный дизайн, который позволил бы это.

Мой вопрос: есть ли естественное расширение этого дизайна, позволяющее посетителю общего назначения также изменять иерархию? Другими словами, есть ли способ, которым посетитель может посетить одного участника, а затем обновить иерархию перед посещением следующего? Совместимо ли это с использованием потоков?


person sprinter    schedule 06.04.2016    source источник


Ответы (1)


По моему предыдущему опыту, шаблон «Посетитель» полезен либо для запроса, либо для воссоздания иерархии. Часть запроса очевидна — вы просто слушаете определенные типы подобъектов, а затем формируете результат запроса по мере необходимости. Другой вопрос, изменение иерархии, сложнее.

Может быть действительно сложно изменить иерархию во время ее повторения. Поэтому я знаю две полезные методики, которые прекрасно работают на практике.

  1. При просмотре иерархии создайте список объектов для изменения. Не меняйте их, пока посещение не будет завершено. Конкретный посетитель может формировать список интересующих его объектов как его частный член. Как только он завершит посещение, в качестве его результата будет представлен список объектов. Только после этого начинайте перебирать полученный список и вносить изменения в объекты.
  2. При посещении иерархии при посещении элемента создайте копию элемента. Если элемент необходимо изменить, создайте измененную версию. В противном случае, если элементы не нужно изменять, просто верните их как новый элемент. После того, как весь визит будет завершен, у вас будет новая иерархия со всеми изменениями, внесенными, как предполагалось. Тогда старая иерархия может быть разыменована, и сборщик мусора соберет те элементы, которые были заменены новыми.

Первый алгоритм применим, когда элементы изменяемы. Второй алгоритм применим, когда элементы неизменяемы.

Надеюсь это поможет.

person Zoran Horvat    schedule 06.08.2016