2016-04-06 14 views
5

Sto utilizzando un modello composito con più classi di nodi foglia che dispongono di operazioni specialistiche e di un modello di visitatore per consentire l'esecuzione di tali operazioni. In questo esempio ho omesso tutti i metodi ovvi accept per chiarezza.Utilizzo di modelli Visitor e Composito per creare un flusso filtrato

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

E 'ora possibile costruire i visitatori per eseguire operazioni quali decrement. Tuttavia trovo più facile per creare un visitatore di uso generale che i flussi di oggetti di una certa classe in modo che qualsiasi operazione può essere eseguita su di loro:

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

Questo è usato come ci si aspetterebbe. Ad esempio:

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

Questo ha una significativa limitazione: non può essere utilizzato per modificare la gerarchia come il flusso viene elaborato. Ad esempio, il seguente errore:

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

Non riesco a pensare a un design elegante alternativo che consentirebbe questo.

La mia domanda è: esiste un'estensione naturale di questo progetto per consentire a un visitatore generico che può anche modificare la gerarchia? In altre parole, c'è un modo in cui il visitatore può visitare un membro, quindi aggiornare la gerarchia prima di visitare il prossimo? È compatibile con l'uso dei flussi?

risposta

0

Nella mia esperienza precedente, il pattern Visitor è utile per interrogare o ricreare la gerarchia. La parte di interrogazione è ovvia: si dovrebbero semplicemente ascoltare determinati tipi di sottooggetti e quindi creare il risultato della query nel modo che si adatta. L'altra domanda, cambiando la gerarchia, è più difficile.

Potrebbe davvero essere difficile cambiare la gerarchia mentre si scorre attraverso di essa. Pertanto, conosco due tecniche utili che funzionano bene nella pratica.

  1. Mentre si visita la gerarchia, creare l'elenco di oggetti da modificare. Non cambiarli fino a quando la visita non è completata. Il visitatore concreto può creare l'elenco di oggetti di interesse come membro privato. Una volta che completa la visita, esporrebbe l'elenco degli oggetti come risultato. Solo dopo inizia a scorrere l'elenco risultante e apportare modifiche a gli oggetti.
  2. Mentre si visita la gerarchia, mentre si visita un elemento, creare una copia di l'elemento. Se l'elemento deve essere modificato, quindi costruire la versione modificata . Altrimenti, se gli elementi non devono essere modificati, è sufficiente restituirlo come nuovo elemento . Al termine della visita, la nuova gerarchia con tutte le modifiche apportate come previsto. La vecchia gerarchia potrebbe essere dereferenziata a e il garbage collector raccoglierà quegli elementi che sono stati sostituiti da con quelli nuovi.

Il primo algoritmo è applicabile quando gli elementi sono mutabili. Il secondo algoritmo è applicabile quando gli elementi sono immutabili.

Spero che questo aiuti.