Visitor

Vantagens

Desvantagens

Exemplos

Exemplo #1

Em certos domínios, é comum existirem processos com regras bem definidas que podem ser descritos através de workflows. Os workflows podem ser definidos através de uma gramática que inclui quatro elementos principais: um comando executável, uma sequência para executar dois ou mais comandos seguidos, uma alternativa para executar um ou outro comando com base em uma condição e uma repetição que executará um mesmo comando até que uma condição seja satisfeita. Devido a essa forma estruturada e predefinida, alguns workflows permitem a automação de seus comandos. Outra característica é a simplicidade de visualização dos workflows por humanos através de fluxogramas, além da facilidade de eles serem entendidos por outros softwares se convertidos para um formato como XML. Dessa forma, o workflow torna-se compartilhável e pode ser reaproveitado, por exemplo, caso uma filial da empresa possua um processo semelhante cujo comportamento poderia também ser automatizado. A interface Action representa um dos elementos do fluxo de trabalho e define a operação accept que recebe um Visitor. A classe CommandAction representa um comando que pode ser executado no fluxo, enquanto a classe SequenceAction representa uma sequência de comandos e a classe abstrata ConditionalAction representa um comando que depende de uma condição, todas herdam de Action e, com exceção de ConditionalAction, implemenam a operação accept. WhileRepetitionAction, UntilRepetitionAction e AlternationAction herdam de ConditionalAction, onde as duas primeiras representam laços de repetição e a terceira uma alternação entre comandos, todas implementando a operação accept. A interface ActionVisitor define uma operação para cada tipo de elemento do fluxo, enquanto a classe InterpretingVisitor implementa ActionVisitor e suas operações. InterpretingVisitor também possui uma referência para um objeto que armazena o estado do alvo das tarefas do fluxo, representado pela classe Object. A classe XMLGeneratingVisitor também implementa ActionVisitor e define as suas operações com o objetivo de gerar o XML do workflow. A classe Client é responsável por instanciar o workflow e o Visitor. As interfaces Command, referenciada por CommandAction, e BooleanExpression, referenciada por ConditionalAction, permitem implementar os comandos e as condições que podem ser definidas no workflow.

Diagrama de Classe

Exemplo #1 - Diagrama

Participantes

  • Visitor (ActionVisitor): Declara a operação Visit para cada classe ConcreteElement na estrutura do objeto.
  • ConcreteVisitor (InterpretingVisitor, XMLGeneratingVisitor): Implementa as operações declaradas pelo Visitor.
  • Element (Action): Define uma operação Accept que recebe um objeto Visitor como parâmetro
  • ConcreteElement (CommandAction, SequenceAction, WhileRepetitionAction, UntilRepetitionAction, AlternationAction): Implementa a operação Accept definida em Element

Código

package visitor;

// Interface que define uma ação de um workflow
public interface Action {

    // Definição da operação
    public void accept(ActionVisitor visitor);

}
package visitor;

// Definição do Visitor do workflow, onde cara operação visit atende a um dos tipos de ação
public interface ActionVisitor {

    public void visit(CommandAction node);
    public void visit(SequenceAction node);
    public void visit(AlternationAction node);
    public void visit(WhileRepetitionAction node);
    public void visit(UntilRepetitionAction node);

}
package visitor;

// Definição de um comando realizado no alvo do workflow
public interface Command {

    public void execute(Object context);

    public Object getValue();
}
package visitor;

// Definição da interface para uma condição booleana
public interface BooleanExpression {

    public boolean evaluate(Object context);

    public Object getParameter();
}
package visitor;

// Implementação da ação que define um laço de repetição enquanto-faça
public class WhileRepetitionAction extends ConditionalAction {

    public WhileRepetitionAction(Action action, BooleanExpression condition) {
        super(action, condition);
    }

    // Implementação da operação que chama o Visitor
    public void accept(ActionVisitor visitor) {
        visitor.visit(this);
    }
}
package visitor;

// Implementação de uma ação terminal que realiza um comando
public class CommandAction implements Action {

    // Comando que será executado
    private final Command command;

    public CommandAction(Command command) {
        this.command = command;
    }

    public Command getCommand() {
        return command;
    }

    // Implementação da operação que chama pelo Visitor
    public void accept(ActionVisitor visitor) {
        visitor.visit(this);
    }
}
package visitor;

// Ação que define uma sequência de duas ações no workflow
public class SequenceAction implements Action {

    // Primeira ação que será executada
    private final Action firstAction;
    // Segunda ação que será executada
    private final Action secondAction;

    public SequenceAction(Action firstAction, Action secondAction) {
        this.firstAction = firstAction;
        this.secondAction = secondAction;
    }

    public Action getFirstAction() {
        return firstAction;
    }

    public Action getSecondAction() {
        return secondAction;
    }

    // Implementação da operação que chama o Visitor
    public void accept(ActionVisitor visitor) {
        visitor.visit(this);
    }
}
package visitor;

// Ação de alternativa do workflow
public class AlternationAction extends ConditionalAction {

    // Ação realizada caso a condição seja falsa (se for null, nenhuma ação é executada)
    private Action falseAction=null;

    public AlternationAction(Action action, BooleanExpression condition, Action falseAction) {
        super(action, condition);
        this.falseAction = falseAction;
    }

    public AlternationAction(Action action, BooleanExpression condition) {
        super(action, condition);
    }

    public Action getFalseAction() {
        return falseAction;
    }

    // Implementação da operação accept que chama o Visitor
    public void accept(ActionVisitor visitor) {
        visitor.visit(this);
    }
}
package visitor;

import visitor.contexts.WaterPumperAgent;

// Implementação do Visitor de interpretação do workflow
public class InterpretingVisitor implements ActionVisitor {

    // O contexto do Visitor é definido nos atributos
    private WaterPumperAgent context;

    public InterpretingVisitor(WaterPumperAgent context) {
        this.context = context;
    }

    // Para uma ação, um elemento terminal, a ação é executada
    public void visit(CommandAction node) {
        node.getCommand().execute(this.context);
    }

    // Para uma sequência da ações, a operação que chama o visitor é chamad apara cada uma delas
    public void visit(SequenceAction node) {
        node.getFirstAction().accept(this);
        node.getSecondAction().accept(this);
    }

    // Para uma alternação entre ações, uma ou outra ação é chamada, dependendo do resultado da condição
    public void visit(AlternationAction node) {
        if(node.getCondition().evaluate(this.context))
            node.getAction().accept(this);
        else {
            Action falseAction = node.getFalseAction();
            if(falseAction != null) falseAction.accept(this);
        }
    }

    // Para um laço enquanto-faça, enquanto a condição for verdadeira a ação é chamada
    public void visit(WhileRepetitionAction node){
        while (node.getCondition().evaluate(this.context)){
            node.getAction().accept(this);
        }
    }

    // Para um laço faça-enquanto, enquanto a condição for falsa a ação é chamada
    public void visit(UntilRepetitionAction node){
        do {
            node.getAction().accept(this);
        } while (!node.getCondition().evaluate(this.context));
    }

}
package visitor;

public class XMLGeneratingVisitor implements ActionVisitor {

    // Para que a string seja acumulada, um atributo XMLString é definido
    private String XMLString="";

    public String getXMLString() {
        return XMLString;
    }

    // Gera o XML para a ação terminal
    public void visit(CommandAction node) {

        XMLString = XMLString.concat("");

        String commandName = node.getCommand().getClass().getSimpleName();

        XMLString = XMLString.concat(
                "<" + commandName + ">" +
                        node.getCommand().getValue().toString() +
                        "");

        XMLString = XMLString.concat("");
    }

    // Gera o XML para as duas ações
    public void visit(SequenceAction node) {

        XMLString = XMLString.concat("");

        node.getFirstAction().accept(this);
        node.getSecondAction().accept(this);

        XMLString = XMLString.concat("");
    }

    // Gera o XML para a condição, a ação caso verdadeira e a ação caso falsa
    public void visit(AlternationAction node) {

        XMLString = XMLString.concat("");

        String conditionName = node.getCondition().getClass().getSimpleName();

        XMLString = XMLString.concat(
                "<" + conditionName + ">" +
                        node.getCondition().getParameter().toString() +
                        "");

        node.getAction().accept(this);

        if(node.getFalseAction() != null) {
            XMLString = XMLString.concat("");
            node.getFalseAction().accept(this);
            XMLString = XMLString.concat("");
        }

        XMLString= XMLString.concat("");
    }

    // Gera o XML para a condição e para a ação
    public void visit(WhileRepetitionAction node){
        XMLString = XMLString.concat("");

        String conditionName = node.getCondition().getClass().getSimpleName();

        XMLString = XMLString.concat(
                "<" + conditionName + ">" +
                        node.getCondition().getParameter().toString() +
                        "");

        node.getAction().accept(this);

        XMLString = XMLString.concat("");
    }

    // Gera o XML para a condição e para a ação
    public void visit(UntilRepetitionAction node){
        XMLString = XMLString.concat("");

        String conditionName = node.getCondition().getClass().getSimpleName();

        XMLString = XMLString.concat(
                "<" + conditionName + ">" +
                        node.getCondition().getParameter().toString() +
                        "");

        node.getAction().accept(this);

        XMLString = XMLString.concat("");
    }
}
package visitor;

// Classe abstrata que define uma ação que possui uma condição
public abstract class ConditionalAction implements Action{

    // Ação que será realizada
    private final Action action;
    // Condição que definirá se a ação será executada ou não
    private final BooleanExpression condition;

    public ConditionalAction(Action action, BooleanExpression condition) {
        this.action = action;
        this.condition = condition;
    }

    public Action getAction() {
        return action;
    }

    public BooleanExpression getCondition() {
        return condition;
    }
}
package visitor;

// Implementação da ação que define um laço de repetição faça-enquanto
public class UntilRepetitionAction extends ConditionalAction {

    public UntilRepetitionAction(Action action, BooleanExpression condition) {
        super(action, condition);
    }

    // Implementação da operação que chama o Visitor
    public void accept(ActionVisitor visitor) {
        visitor.visit(this);
    }
}
package visitor;

import visitor.contexts.WaterPumperAgent;
import visitor.commands.AddTemperatureCommand;
import visitor.commands.AddWaterLevelCommand;
import visitor.commands.SetPumperOnCommand;
import visitor.expressions.MaxTemperatureExpression;
import visitor.expressions.MaxWaterLevelExpression;
import visitor.expressions.PumperOnExpression;

public class Client {

    public static void main(String[] args) {

        // Retorna uma ação inicial de um workflow
        Action workflow = defineWorkflow();

        // Instancia o context (objeto alvo do workflow)
        WaterPumperAgent agent = new WaterPumperAgent(false, 0, 20);

        // Instância do Visitor que interpretará o workflow
        InterpretingVisitor interpretingVisitor = new InterpretingVisitor(agent);

        // Exibe as informações do context antes da interpretação
        System.out.println("Agent status:\nPumper on: " + agent.isPumperOn() +
                "\nWater Level: " + agent.getWaterLevel() +
                "\nTemperature: " + agent.getTemperature() + "\n");

        // Chamada do interpretingVisitor para interpretar o workflow
        workflow.accept(interpretingVisitor);

        // Exibe as informações do context após a interpretação
        System.out.println("Agent status:\nPumper on: " + agent.isPumperOn() +
                "\nWater Level: " + agent.getWaterLevel() +
                "\nTemperature: " + agent.getTemperature() + "\n");

        // Instância do Visitor que gerará o XML do workflow
        XMLGeneratingVisitor xmlVisitor = new XMLGeneratingVisitor();

        // Chamada do xmlVisitor que gerará o XML
        workflow.accept(xmlVisitor);
        System.out.println(xmlVisitor.getXMLString());
    }

    public static Action defineWorkflow() {

        // Inicio do workflow
        // Se a bomba estiver desligada, ligar bomba. Se não, fim do workflow.
        // Enquanto o nivel da agua estiver abaixo de 10, aumentar o nivel da agua em 1 e a temperatura em 5
        // Caso a temperatura seja superior a 50, desligar a bomba
        // Fim do workflow

        //commands
        SetPumperOnCommand pumperOnCommand = new SetPumperOnCommand(true);
        SetPumperOnCommand pumperOffCommand = new SetPumperOnCommand(false);
        AddWaterLevelCommand addWaterLevelCommand = new AddWaterLevelCommand(1);
        AddTemperatureCommand addTemperatureCommand = new AddTemperatureCommand(5);

        //expressions
        PumperOnExpression pumperNotOnExpression = new PumperOnExpression(false);
        MaxWaterLevelExpression maxWaterLevelExpression = new MaxWaterLevelExpression(10);
        MaxTemperatureExpression maxTemperatureExpression = new MaxTemperatureExpression(50);

        // Definição do Workflow
        // Elementos terminais
        CommandAction turnOnPumper = new visitor.CommandAction(pumperOnCommand);
        CommandAction turnOffPumper = new visitor.CommandAction(pumperOffCommand);
        CommandAction addToTemperature = new visitor.CommandAction(addTemperatureCommand);
        CommandAction addWaterLevel = new CommandAction(addWaterLevelCommand);

        // Elementos não terminais
        SequenceAction addWaterLevelThenAddTemperature = new visitor.SequenceAction(addWaterLevel, addToTemperature);
        AlternationAction verifyTemperature = new visitor.AlternationAction(turnOffPumper, maxTemperatureExpression);

        WhileRepetitionAction doUntilMaxWaterLevel = new WhileRepetitionAction(addWaterLevelThenAddTemperature, maxWaterLevelExpression);

        SequenceAction startPumperProcess = new visitor.SequenceAction(doUntilMaxWaterLevel, verifyTemperature);
        SequenceAction startWorkflow = new SequenceAction(turnOnPumper, startPumperProcess);

        return new AlternationAction(startWorkflow, pumperNotOnExpression);
    }
}
package visitor.commands;

import visitor.Command;
import visitor.contexts.WaterPumperAgent;

public class AddTemperatureCommand implements Command {

    private float temperature;

    public AddTemperatureCommand(float temperature) {
        this.temperature = temperature;
    }

    public void execute(Object context) {
        WaterPumperAgent agent = (WaterPumperAgent) context;
        agent.setTemperature(agent.getTemperature() + this.temperature);
    }

    public Object getValue() {
        return this.temperature;
    }
}
package visitor.commands;

import visitor.Command;
import visitor.contexts.WaterPumperAgent;

public class AddWaterLevelCommand implements Command {

    private int level;

    public AddWaterLevelCommand(int level) {
        this.level = level;
    }

    public void execute(Object context) {
        WaterPumperAgent agent = (WaterPumperAgent) context;
        agent.setWaterLevel(agent.getWaterLevel() + this.level);
    }

    public Object getValue() {
        return this.level;
    }
}
package visitor.commands;

import visitor.Command;
import visitor.contexts.WaterPumperAgent;

public class SetPumperOnCommand implements Command {

    private boolean pumperOn;

    public SetPumperOnCommand(boolean pumperOn) {
        this.pumperOn = pumperOn;
    }

    public void execute(Object context) {
        WaterPumperAgent agent = (WaterPumperAgent)context;
        agent.setPumperOn(this.pumperOn);
    }

    public Object getValue() {
        return this.pumperOn;
    }
}
package visitor.expressions;

import visitor.BooleanExpression;
import visitor.contexts.WaterPumperAgent;

public class MaxTemperatureExpression implements BooleanExpression {

    private final float temperature;

    public MaxTemperatureExpression(float temperature) {
        this.temperature = temperature;
    }

    public boolean evaluate(Object context) {
        WaterPumperAgent agent = (WaterPumperAgent) context;
        return agent.getTemperature() >= temperature;
    }

    public Object getParameter() {
        return this.temperature;
    }

}
package visitor.expressions;

import visitor.BooleanExpression;
import visitor.contexts.WaterPumperAgent;

public class MaxWaterLevelExpression implements BooleanExpression {

    private final int waterLevel;

    public MaxWaterLevelExpression(int waterLevel) {
        this.waterLevel = waterLevel;
    }

    public boolean evaluate(Object context) {
        WaterPumperAgent agent = (WaterPumperAgent) context;
        return agent.getWaterLevel() < waterLevel;
    }

    public Object getParameter() {
        return this.waterLevel;
    }

}
package visitor.expressions;

import visitor.BooleanExpression;
import visitor.contexts.WaterPumperAgent;

public class PumperOnExpression implements BooleanExpression {

    private final boolean pumperIsOn;

    public PumperOnExpression(boolean pumperIsOn) {
        this.pumperIsOn = pumperIsOn;
    }

    public boolean evaluate(Object context) {
        WaterPumperAgent agent = (WaterPumperAgent)context;
        return agent.isPumperOn() == pumperIsOn;
    }

    public Object getParameter() {
        return this.pumperIsOn;
    }
}
package visitor.contexts;

public class WaterPumperAgent {

    private boolean pumperOn;
    private int waterLevel;
    private float temperature;

    public WaterPumperAgent(boolean pumperOn, int waterLevel, float temperature) {
        this.pumperOn = pumperOn;
        this.waterLevel = waterLevel;
        this.temperature = temperature;
    }

    public boolean isPumperOn() {
        return pumperOn;
    }

    public void setPumperOn(boolean pumperOn) {
        this.pumperOn = pumperOn;
    }

    public int getWaterLevel() {
        return waterLevel;
    }

    public void setWaterLevel(int waterLevel) {
        this.waterLevel = waterLevel;
    }

    public float getTemperature() {
        return temperature;
    }

    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

}
Clique aqui para fazer o download do código completo de implementação deste Design Pattern.

Padrões Relacionados

Este Padrão pode ser usado para resolver os seguintes problemas: