Difference between revisions of "Visitor (padrão de desenho)/Expressões Aritméticas Simples"

From Wiki**3

< Visitor (padrão de desenho)
(Created page with "O exemplo seguinte mostra um exemplo de uso do padrão Visitor. O ponto de partida é uma hierarquia de expressões inteiras: neste exemplo, usam-se as funções aritméticas si...")
 
 
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
 +
{{TOCright}}
 
O exemplo seguinte mostra um exemplo de uso do padrão Visitor.
 
O exemplo seguinte mostra um exemplo de uso do padrão Visitor.
  
Line 6: Line 7:
  
 
Nesta classe, o método '''value()''' calcula o valor da expressão recursivamente, primeiro, pelo lado esquerdo ('''left()''') e, depois, pelo lado direito ('''right()''').
 
Nesta classe, o método '''value()''' calcula o valor da expressão recursivamente, primeiro, pelo lado esquerdo ('''left()''') e, depois, pelo lado direito ('''right()''').
<java5>
+
<source lang="java">
 
public class ADD extends BinaryFunction {
 
public class ADD extends BinaryFunction {
 
   public int value() {
 
   public int value() {
Line 12: Line 13:
 
   }
 
   }
 
}
 
}
</java5>
+
</source>
  
 
Esta implementação é suficiente para a avaliação da expressão. No entanto, que seria necessário alterar para conseguir utilizar a expressão '''ADD''' para outro fim? Por exemplo, para a traduzir para C? ou para outro fim?
 
Esta implementação é suficiente para a avaliação da expressão. No entanto, que seria necessário alterar para conseguir utilizar a expressão '''ADD''' para outro fim? Por exemplo, para a traduzir para C? ou para outro fim?
Line 24: Line 25:
 
As classes das expressões estão organizadas segundo o padrão Composite e assentam sobre uma interface de topo. Escolheu-se uma interface e não uma classe abstracta por simplicidade e porque não há consequências relativamente a repetição de código.
 
As classes das expressões estão organizadas segundo o padrão Composite e assentam sobre uma interface de topo. Escolheu-se uma interface e não uma classe abstracta por simplicidade e porque não há consequências relativamente a repetição de código.
  
<java5>
+
<source lang="java">
 
public interface Content {
 
public interface Content {
 
   int accept(Visitor v);
 
   int accept(Visitor v);
 
}
 
}
</java5>
+
</source>
  
 
Note-se que a interface, embora seja um tipo abstracto (nunca sendo realmente processada pelo "visitor"), tem de declarar uma interface comum a todas as expressões. Dada a simplicidade do cenário, apenas é necessário o método '''accept'''. Note-se que não é especificado qual o "visitor" específico.
 
Note-se que a interface, embora seja um tipo abstracto (nunca sendo realmente processada pelo "visitor"), tem de declarar uma interface comum a todas as expressões. Dada a simplicidade do cenário, apenas é necessário o método '''accept'''. Note-se que não é especificado qual o "visitor" específico.
Line 35: Line 36:
  
 
Note-se a implementação de '''accept'''.
 
Note-se a implementação de '''accept'''.
<java5>
+
<source lang="java">
 
public class Literal implements Content {
 
public class Literal implements Content {
 
   private int _value;
 
   private int _value;
Line 42: Line 43:
 
   public int value() { return _value; }
 
   public int value() { return _value; }
 
}
 
}
</java5>
+
</source>
  
 
A classe de base para as funções binárias limita-se a definir os atributos e métodos relacionados. Sendo abstracta, não necessita definir '''accept''' (será definido por todas as suas subclasses concretas).
 
A classe de base para as funções binárias limita-se a definir os atributos e métodos relacionados. Sendo abstracta, não necessita definir '''accept''' (será definido por todas as suas subclasses concretas).
  
<java5>
+
<source lang="java">
 
public abstract class BinaryFunction implements Content {
 
public abstract class BinaryFunction implements Content {
 
   private Content _left;
 
   private Content _left;
Line 54: Line 55:
 
   public Content right() { return _right; }
 
   public Content right() { return _right; }
 
}
 
}
</java5>
+
</source>
  
 
Finalmente, as quatro classes das expressões aritméticas simples (soma, subtracção, multiplicação, divisão), são definidas. Note-se a extrema simplicidade destas classes: ao ser utilizado um "visitor", o comportamento delas migrou para as classes do padrão Visitor, deixando apenas a definição da inicialização e o método '''accept'''.
 
Finalmente, as quatro classes das expressões aritméticas simples (soma, subtracção, multiplicação, divisão), são definidas. Note-se a extrema simplicidade destas classes: ao ser utilizado um "visitor", o comportamento delas migrou para as classes do padrão Visitor, deixando apenas a definição da inicialização e o método '''accept'''.
  
<java5>
+
<source lang="java">
 
public class ADD extends BinaryFunction {
 
public class ADD extends BinaryFunction {
 
   public ADD(Content left, Content right) { super(left, right); }
 
   public ADD(Content left, Content right) { super(left, right); }
 
   public int accept(Visitor v) { return v.visitADD(this); }
 
   public int accept(Visitor v) { return v.visitADD(this); }
 
}
 
}
</java5>
+
</source>
  
<java5>
+
<source lang="java">
 
public class SUB extends BinaryFunction {
 
public class SUB extends BinaryFunction {
 
   public SUB(Content left, Content right) { super(left, right); }
 
   public SUB(Content left, Content right) { super(left, right); }
 
   public int accept(Visitor v) { return v.visitSUB(this); }
 
   public int accept(Visitor v) { return v.visitSUB(this); }
 
}
 
}
</java5>
+
</source>
  
<java5>
+
<source lang="java">
 
public class MUL extends BinaryFunction {
 
public class MUL extends BinaryFunction {
 
   public MUL(Content left, Content right) { super(left, right); }
 
   public MUL(Content left, Content right) { super(left, right); }
 
   public int accept(Visitor v) { return v.visitMUL(this); }
 
   public int accept(Visitor v) { return v.visitMUL(this); }
 
}
 
}
</java5>
+
</source>
  
<java5>
+
<source lang="java">
 
public class DIV extends BinaryFunction {
 
public class DIV extends BinaryFunction {
 
   public DIV(Content left, Content right) { super(left, right); }
 
   public DIV(Content left, Content right) { super(left, right); }
 
   public int accept(Visitor v) { return v.visitDIV(this); }
 
   public int accept(Visitor v) { return v.visitDIV(this); }
 
}
 
}
</java5>
+
</source>
  
 
== Classes de Visita ("visitors") (Visitor) ==
 
== Classes de Visita ("visitors") (Visitor) ==
Line 90: Line 91:
 
A interface '''Visitor''' define os métodos que devem ser implementados por todas as implementações. Implementações particulares podem definir outros métodos ou mesmo atributos. Por simplicidade, declara-se o retorno como um inteiro (isto permite que sejam obtidos valores recursivamente quando for apropriado).
 
A interface '''Visitor''' define os métodos que devem ser implementados por todas as implementações. Implementações particulares podem definir outros métodos ou mesmo atributos. Por simplicidade, declara-se o retorno como um inteiro (isto permite que sejam obtidos valores recursivamente quando for apropriado).
  
<java5>
+
<source lang="java">
 
public interface Visitor {
 
public interface Visitor {
 
   int visitADD(ADD c);
 
   int visitADD(ADD c);
Line 98: Line 99:
 
   int visitLiteral(Literal c);
 
   int visitLiteral(Literal c);
 
}
 
}
</java5>
+
</source>
  
 
No exemplo que aqui se apresenta, define-se, primeiro, um "visitor" para avaliação de expressões. A sua passagem a um método '''accept''' é como a chamada, no modelo original, ao método '''value''' sobre a classe original correspondente.
 
No exemplo que aqui se apresenta, define-se, primeiro, um "visitor" para avaliação de expressões. A sua passagem a um método '''accept''' é como a chamada, no modelo original, ao método '''value''' sobre a classe original correspondente.
  
<java5>
+
<source lang="java">
 
public class Evaluator implements Visitor {
 
public class Evaluator implements Visitor {
 
   public int visitADD(ADD c) {
 
   public int visitADD(ADD c) {
Line 120: Line 121:
 
   }  
 
   }  
 
}
 
}
</java5>
+
</source>
  
 
Como se pode ver, a recursão termina nos literais. Compare-se ainda esta implementação com a classe '''ADD''' mostrada acima.
 
Como se pode ver, a recursão termina nos literais. Compare-se ainda esta implementação com a classe '''ADD''' mostrada acima.
Line 126: Line 127:
 
De seguida, para demonstrar como a funcionalidade pode ser muito diferente de classe de visita para classe de visita, mostra-se a classe '''CWriter''' que é capaz de produzir o código C para avaliar uma expressão.
 
De seguida, para demonstrar como a funcionalidade pode ser muito diferente de classe de visita para classe de visita, mostra-se a classe '''CWriter''' que é capaz de produzir o código C para avaliar uma expressão.
  
<java5>
+
<source lang="java">
 
public class CWriter implements Visitor {
 
public class CWriter implements Visitor {
 
   String _cProgram = "";
 
   String _cProgram = "";
Line 172: Line 173:
 
   }
 
   }
 
}
 
}
</java5>
+
</source>
  
 
A ideia é produzir uma cadeia de caracteres com o programa C completo. De seguida, o método '''toString''' (o processo poderia ser outro) apresenta o programa equivalente. Utiliza-se este método por simplicidade: numa outra situação, o seu nome poderia ser outro.
 
A ideia é produzir uma cadeia de caracteres com o programa C completo. De seguida, o método '''toString''' (o processo poderia ser outro) apresenta o programa equivalente. Utiliza-se este método por simplicidade: numa outra situação, o seu nome poderia ser outro.
Line 180: Line 181:
 
Finalmente, a aplicação começa por criar a composição (simulando uma fábrica) e envia instâncias das duas classes de visita, imprimindo os respectivos resultados.
 
Finalmente, a aplicação começa por criar a composição (simulando uma fábrica) e envia instâncias das duas classes de visita, imprimindo os respectivos resultados.
  
<java5>
+
<source lang="java">
 
public class App {
 
public class App {
 
   public static void main(String[] args) {
 
   public static void main(String[] args) {
Line 195: Line 196:
 
   }
 
   }
 
}
 
}
</java5>
+
</source>
  
 
== Resultados ==
 
== Resultados ==
Line 205: Line 206:
 
A saída é a seguinte:
 
A saída é a seguinte:
  
<text>
+
<source lang="text">
 
1 + 2 * 3 = 7
 
1 + 2 * 3 = 7
 
==========================================
 
==========================================
Line 213: Line 214:
 
         return 0;
 
         return 0;
 
}
 
}
</text>
+
</source>
  
 
[[category:Ensino]]
 
[[category:Ensino]]
[[catagory:PO]]
+
[[category:PO]]
 
[[category:PO Exemplos]]
 
[[category:PO Exemplos]]
[[categoru:Java]]
+
[[category:Java]]

Latest revision as of 16:48, 21 February 2019

O exemplo seguinte mostra um exemplo de uso do padrão Visitor.

O ponto de partida é uma hierarquia de expressões inteiras: neste exemplo, usam-se as funções aritméticas simples e um valor literal.

Para motivar o uso do padrão, considere-se a class ADD (soma binária), definida como se segue (BinaryFunction é uma superclasse abstracta comum a todas as operações binárias, que contém dois atributos do tipo Content (a superclasse de todas as expressões; neste primeiro exemplo, assume-se que declara o método value(), que retorna o valor calculado).

Nesta classe, o método value() calcula o valor da expressão recursivamente, primeiro, pelo lado esquerdo (left()) e, depois, pelo lado direito (right()).

public class ADD extends BinaryFunction {
  public int value() {
    return left().value() + right().value();
  }
}

Esta implementação é suficiente para a avaliação da expressão. No entanto, que seria necessário alterar para conseguir utilizar a expressão ADD para outro fim? Por exemplo, para a traduzir para C? ou para outro fim?

As respostas àquelas perguntas poderiam ser todas "acrescentar um método para esse fim". No entanto, isso vem alterar a definição original da classe e novas evoluções são igualmente acompanhadas de impacto negativo e generalizado sobre este tipo de classes (as alterações teriam, potencialmente, que ser realizadas também em classes semelhantes).

Utilizando o padrão Visitor, é possível remover as dependências de implementações específicas das classes de uma hierarquia de dados (esta hierarquia é, frequentemente, um Composite criado por uma fábrica).

Classes das Expressões (Composite)

As classes das expressões estão organizadas segundo o padrão Composite e assentam sobre uma interface de topo. Escolheu-se uma interface e não uma classe abstracta por simplicidade e porque não há consequências relativamente a repetição de código.

public interface Content {
  int accept(Visitor v);
}

Note-se que a interface, embora seja um tipo abstracto (nunca sendo realmente processada pelo "visitor"), tem de declarar uma interface comum a todas as expressões. Dada a simplicidade do cenário, apenas é necessário o método accept. Note-se que não é especificado qual o "visitor" específico.

Abaixo desta interface existem duas classes que a implementam: a dos literais (Literal) e a das funções binárias (BinaryFunction). Assume-se que os literais são os únicos que guardam valores inteiros e que todas as outras expressões se definem recursivamente (Composite).

Note-se a implementação de accept.

public class Literal implements Content {
  private int _value;
  public Literal(int value) { _value = value; }
  public int accept(Visitor v) { return v.visitLiteral(this); }
  public int value() { return _value; }
}

A classe de base para as funções binárias limita-se a definir os atributos e métodos relacionados. Sendo abstracta, não necessita definir accept (será definido por todas as suas subclasses concretas).

public abstract class BinaryFunction implements Content {
  private Content _left;
  private Content _right;
  public BinaryFunction(Content left, Content right) { _left = left; _right = right; }
  public Content left() { return _left; }
  public Content right() { return _right; }
}

Finalmente, as quatro classes das expressões aritméticas simples (soma, subtracção, multiplicação, divisão), são definidas. Note-se a extrema simplicidade destas classes: ao ser utilizado um "visitor", o comportamento delas migrou para as classes do padrão Visitor, deixando apenas a definição da inicialização e o método accept.

public class ADD extends BinaryFunction {
  public ADD(Content left, Content right) { super(left, right); }
  public int accept(Visitor v) { return v.visitADD(this); }
}
public class SUB extends BinaryFunction {
  public SUB(Content left, Content right) { super(left, right); }
  public int accept(Visitor v) { return v.visitSUB(this); }
}
public class MUL extends BinaryFunction {
  public MUL(Content left, Content right) { super(left, right); }
  public int accept(Visitor v) { return v.visitMUL(this); }
}
public class DIV extends BinaryFunction {
  public DIV(Content left, Content right) { super(left, right); }
  public int accept(Visitor v) { return v.visitDIV(this); }
}

Classes de Visita ("visitors") (Visitor)

A interface Visitor define os métodos que devem ser implementados por todas as implementações. Implementações particulares podem definir outros métodos ou mesmo atributos. Por simplicidade, declara-se o retorno como um inteiro (isto permite que sejam obtidos valores recursivamente quando for apropriado).

public interface Visitor {
  int visitADD(ADD c);
  int visitSUB(SUB c);
  int visitMUL(MUL c);
  int visitDIV(DIV c);
  int visitLiteral(Literal c);
}

No exemplo que aqui se apresenta, define-se, primeiro, um "visitor" para avaliação de expressões. A sua passagem a um método accept é como a chamada, no modelo original, ao método value sobre a classe original correspondente.

public class Evaluator implements Visitor {
  public int visitADD(ADD c) {
    return c.left().accept(this) + c.right().accept(this);
  } 
  public int visitSUB(SUB c) {
    return c.left().accept(this) - c.right().accept(this);
  } 
  public int visitMUL(MUL c) {
    return c.left().accept(this) * c.right().accept(this);
  } 
  public int visitDIV(DIV c) {
    return c.left().accept(this) / c.right().accept(this);
  } 
  public int visitLiteral(Literal c) {
    return c.value();
  } 
}

Como se pode ver, a recursão termina nos literais. Compare-se ainda esta implementação com a classe ADD mostrada acima.

De seguida, para demonstrar como a funcionalidade pode ser muito diferente de classe de visita para classe de visita, mostra-se a classe CWriter que é capaz de produzir o código C para avaliar uma expressão.

public class CWriter implements Visitor {
  String _cProgram = "";
  public String toString() {
    String program = "#include <stdio.h>\nint main() {\n\tprintf(\"%d\\n\", ";
    program += _cProgram;
    program += ");\n\treturn 0;\n}\n";
    return program;
  }
  public int visitADD(ADD c) {
    _cProgram += "(";
    c.left().accept(this);
    _cProgram += "+";
    c.right().accept(this);
    _cProgram += ")";
    return 0;
  }
  public int visitSUB(SUB c) {
    _cProgram += "(";
    c.left().accept(this);
    _cProgram += "-";
    c.right().accept(this);
    _cProgram += ")";
    return 0;
  }
  public int visitMUL(MUL c) {
    _cProgram += "(";
    c.left().accept(this);
    _cProgram += "*";
    c.right().accept(this);
    _cProgram += ")";
    return 0;
  }
  public int visitDIV(DIV c) {
    _cProgram += "(";
    c.left().accept(this);
    _cProgram += "/";
    c.right().accept(this);
    _cProgram += ")";
    return 0;
  }
  public int visitLiteral(Literal c) {
    _cProgram += Integer.toString(c.value());
    return 0;
  }
}

A ideia é produzir uma cadeia de caracteres com o programa C completo. De seguida, o método toString (o processo poderia ser outro) apresenta o programa equivalente. Utiliza-se este método por simplicidade: numa outra situação, o seu nome poderia ser outro.

Aplicação

Finalmente, a aplicação começa por criar a composição (simulando uma fábrica) e envia instâncias das duas classes de visita, imprimindo os respectivos resultados.

public class App {
  public static void main(String[] args) {
    // 1 + 2 * 3
    Content c = new ADD(new Literal(1), new MUL(new Literal(2), new Literal(3)));
    //
    int value = c.accept(new Evaluator());
    System.out.println("1 + 2 * 3 = " + value);
    //
    Visitor cWriter = new CWriter();
    c.accept(cWriter);
    System.out.println("==========================================");
    System.out.println(cWriter);
  }
}

Resultados

O resultado é obtido através da execução do método main (assume-se a compilação prévia de todas as classes):

java App

A saída é a seguinte:

1 + 2 * 3 = 7
==========================================
#include <stdio.h>
int main() {
        printf("%d\n", (1+(2*3)));
        return 0;
}