Visitor
O Visitor permite adicionar novas operações a uma estrutura de objetos sem modificar as classes desses objetos.
Ele é especialmente útil quando temos uma estrutura de objetos estável, mas precisamos adicionar constantemente novas operações sobre ela.
Imagine uma árvore de elementos:
Document
├── Text
├── Image
└── Video
Podemos querer realizar operações como:
Exportar para HTML
Exportar para PDF
Calcular tamanho
Validar
Gerar estatísticas
Sem Visitor, poderíamos adicionar vários métodos diretamente nas classes.
Com Visitor, cada operação fica separada.
Elementos
Criamos:
interface Element {
accept(
visitor: Visitor
): void;
}
Texto:
class TextElement
implements Element {
constructor(
public content: string
) {}
accept(
visitor: Visitor
) {
visitor.visitText(this);
}
}
Imagem:
class ImageElement
implements Element {
constructor(
public url: string
) {}
accept(
visitor: Visitor
) {
visitor.visitImage(this);
}
}
Visitor
Criamos:
interface Visitor {
visitText(
element: TextElement
): void;
visitImage(
element: ImageElement
): void;
}
Agora podemos criar um Visitor para exportar HTML:
class HtmlVisitor
implements Visitor {
visitText(
element: TextElement
) {
console.log(
`<p>${element.content}</p>`
);
}
visitImage(
element: ImageElement
) {
console.log(
`<img src="${element.url}" />`
);
}
}
Uso:
const elements: Element[] = [
new TextElement(
"Olá mundo"
),
new ImageElement(
"image.png"
)
];
Executamos:
const visitor =
new HtmlVisitor();
for (
const element
of elements
) {
element.accept(visitor);
}
Resultado:
<p>Olá mundo</p>
<img src="image.png" />
Adicionando uma nova operação
Agora queremos gerar Markdown.
Não precisamos alterar:
TextElement
ImageElement
Criamos outro Visitor:
class MarkdownVisitor
implements Visitor {
visitText(
element: TextElement
) {
console.log(
element.content
);
}
visitImage(
element: ImageElement
) {
console.log(
``
);
}
}
Agora podemos usar:
const markdown =
new MarkdownVisitor();
for (
const element
of elements
) {
element.accept(markdown);
}
A mesma estrutura pode ser processada de diferentes maneiras.
Quando usar?
Visitor é útil quando:
- Temos uma estrutura de objetos relativamente estável.
- Precisamos adicionar muitas operações diferentes.
- Queremos separar operações das classes de dados.
- Adicionar novos comportamentos é mais comum do que adicionar novos tipos de objetos.
Exemplos:
ASTs
Compiladores
Interpretadores
Documentos
Sistemas de arquivos
Estruturas de dados complexas
Uma desvantagem é que adicionar um novo tipo de elemento pode exigir alterações em todos os Visitors.
A ideia principal é:
Separar operações da estrutura de objetos sobre a qual essas operações são executadas.