Eu continuo vendo referências ao padrão de visitantes em blogs, mas tenho que admitir, simplesmente não entendo. Li o artigo da Wikipedia para o padrão e entendo sua mecânica, mas ainda estou confuso sobre quando o usaria.
Como alguém que recentemente adquiriu o padrão do decorador e agora está vendo usos para ele em todos os lugares, eu gostaria de poder entender intuitivamente esse padrão aparentemente útil também.
design-patterns
visitor-pattern
George Mauer
fonte
fonte
Respostas:
Não estou muito familiarizado com o padrão de visitantes. Vamos ver se eu entendi direito. Suponha que você tenha uma hierarquia de animais
(Suponha que seja uma hierarquia complexa com uma interface bem estabelecida.)
Agora, queremos adicionar uma nova operação à hierarquia, ou seja, queremos que cada animal faça seu som. No que diz respeito à hierarquia, é simples: você pode fazê-lo com polimorfismo direto:
Mas, procedendo dessa maneira, toda vez que você deseja adicionar uma operação, você deve modificar a interface para todas as classes da hierarquia. Agora, suponha que você esteja satisfeito com a interface original e que deseje fazer o menor número possível de modificações.
O padrão Visitor permite mover cada nova operação em uma classe adequada e você precisa estender a interface da hierarquia apenas uma vez. Vamos fazer isso. Primeiro, definimos uma operação abstrata (a classe "Visitor" no GoF ) que possui um método para todas as classes na hierarquia:
Em seguida, modificamos a hierarquia para aceitar novas operações:
Por fim, implementamos a operação real, sem modificar nem Cat nem Dog :
Agora você tem uma maneira de adicionar operações sem modificar mais a hierarquia. Aqui está como funciona:
fonte
letsDo(Operation *v)
precisa de um ponteiro.theSound.hereIsACat(c)
teria feito o trabalho, como você justifica toda a sobrecarga introduzida pelo padrão? expedição dupla é a justificativa.A razão da sua confusão provavelmente é que o Visitante é um nome impróprio fatal. Muitos programadores (de destaque 1 !) Tropeçaram nesse problema. O que ele realmente faz é implementar o envio duplo em idiomas que não o suportam nativamente (a maioria deles não).
1) Meu exemplo favorito é Scott Meyers, aclamado autor de "Effective C ++", que chamou esse de seu mais importante C ++ aha! momentos de sempre .
fonte
switch
:switch
rígidos códigos de tomada de decisão no lado do cliente (duplicação de código) e não oferece verificação de tipo estático ( verifique a integridade e distinção de casos, etc.). Um padrão de visitante é verificado pelo verificador de tipos e, geralmente, simplifica o código do cliente.virtual
que os recursos semelhantes são tão úteis nas linguagens de programação modernas - eles são o alicerce básico dos programas extensíveis - na minha opinião, a maneira c (chave aninhada ou correspondência de padrões, etc., dependendo do idioma de sua escolha) é muito mais limpo em código que não precisa ser extensível e fiquei agradavelmente surpreso ao ver esse estilo em software complicado como o prover 9. Mais importante, qualquer linguagem que queira fornecer extensibilidade provavelmente deve acomodar melhores padrões de despacho do que despacho único recursivo (ou seja, Visitante).Todos aqui estão corretos, mas acho que não aborda o "quando". Primeiro, em Design Patterns:
Agora, vamos pensar em uma hierarquia de classes simples. Eu tenho as classes 1, 2, 3 e 4 e os métodos A, B, C e D. Coloque-os como em uma planilha: as classes são linhas e os métodos são colunas.
Agora, o design orientado a objetos pressupõe que você tem mais chances de criar novas classes do que novos métodos, portanto, adicionar mais linhas, por assim dizer, é mais fácil. Você acabou de adicionar uma nova classe, especificar o que é diferente nessa classe e herdar o restante.
Às vezes, porém, as classes são relativamente estáticas, mas você precisa adicionar mais métodos com freqüência - adicionando colunas. A maneira padrão em um design de OO seria adicionar esses métodos a todas as classes, o que pode ser caro. O padrão Visitor facilita isso.
A propósito, esse é o problema que o padrão de Scala combina com a intenção de resolver.
fonte
O padrão de design do Visitor funciona muito bem para estruturas "recursivas", como árvores de diretório, estruturas XML ou contornos de documentos.
Um objeto Visitor visita cada nó na estrutura recursiva: cada diretório, cada tag XML, qualquer que seja. O objeto Visitor não faz um loop pela estrutura. Em vez disso, os métodos Visitor são aplicados a cada nó da estrutura.
Aqui está uma estrutura típica de nó recursivo. Pode ser um diretório ou uma marca XML. [Se você é uma pessoa Java, imagine vários métodos extras para criar e manter a lista de filhos.]
O
visit
método aplica um objeto Visitor a cada nó na estrutura. Nesse caso, é um visitante de cima para baixo. Você pode alterar a estrutura dovisit
método para fazer de baixo para cima ou de alguma outra ordem.Aqui está uma superclasse para os visitantes. É usado pelo
visit
método Ele "chega a" cada nó da estrutura. Desde que ovisit
método chamaup
edown
, o visitante pode acompanhar a profundidade.Uma subclasse pode fazer coisas como contar nós em cada nível e acumular uma lista de nós, gerando um bom número de seção hierárquica.
Aqui está uma aplicação. Constrói uma estrutura de árvore
someTree
. Ele cria umVisitor
,dumpNodes
.Em seguida, aplica-se
dumpNodes
à árvore. OdumpNode
objeto "visitará" cada nó na árvore.O
visit
algoritmo TreeNode garantirá que cada TreeNode seja usado como argumento para oarrivedAt
método do Visitor .fonte
Uma maneira de ver isso é que o padrão de visitante é uma maneira de permitir que seus clientes adicionem métodos adicionais a todas as suas classes em uma hierarquia de classes específica.
É útil quando você possui uma hierarquia de classes razoavelmente estável, mas possui requisitos de alteração do que precisa ser feito com essa hierarquia.
O exemplo clássico é para compiladores e similares. Uma árvore de sintaxe abstrata (AST) pode definir com precisão a estrutura da linguagem de programação, mas as operações que você deseja executar no AST serão alteradas à medida que o projeto avança: geradores de código, impressoras bonitas, depuradoras, análise de métricas de complexidade.
Sem o Padrão do Visitante, toda vez que um desenvolvedor quisesse adicionar um novo recurso, ele precisaria adicionar esse método a todos os recursos da classe base. Isso é particularmente difícil quando as classes base aparecem em uma biblioteca separada ou são produzidas por uma equipe separada.
(Ouvi dizer que o padrão Visitor está em conflito com boas práticas de OO, porque afasta as operações dos dados dos dados. O padrão Visitor é útil precisamente na situação em que as práticas normais de OO falham.)
fonte
Há pelo menos três boas razões para usar o Padrão de Visitante:
Reduza a proliferação de código, que é apenas ligeiramente diferente quando as estruturas de dados são alteradas.
Aplique a mesma computação a várias estruturas de dados, sem alterar o código que implementa a computação.
Adicione informações às bibliotecas herdadas sem alterar o código herdado.
Por favor, dê uma olhada em um artigo que escrevi sobre isso .
fonte
Como Konrad Rudolph já apontou, é adequado para casos em que precisamos de expedição dupla
Aqui está um exemplo para mostrar uma situação em que precisamos de expedição dupla e como o visitante nos ajuda a fazê-lo.
Exemplo:
Digamos que eu tenho três tipos de dispositivos móveis - iPhone, Android, Windows Mobile.
Todos esses três dispositivos têm um rádio Bluetooth instalado.
Vamos supor que o rádio do dente azul possa ser de 2 OEMs separados - Intel e Broadcom.
Apenas para tornar o exemplo relevante para a nossa discussão, vamos também assumir que as APIs expostas pelo rádio Intel são diferentes daquelas expostas pelo rádio Broadcom.
É assim que minhas aulas ficam -
Agora, eu gostaria de introduzir uma operação - Ligar o Bluetooth no dispositivo móvel.
Sua assinatura de função deve se parecer com algo assim -
Portanto, dependendo do tipo certo de dispositivo e Dependendo do tipo certo de rádio Bluetooth , ele pode ser ligado chamando as etapas ou algoritmos apropriados .
Principalmente, torna-se uma matriz 3 x 2, onde estou tentando vetorizar a operação correta, dependendo do tipo certo de objetos envolvidos.
Um comportamento polimórfico, dependendo do tipo de ambos os argumentos.
Agora, o padrão de visitante pode ser aplicado a esse problema. A inspiração vem da página da Wikipedia, afirmando: “Em essência, o visitante permite adicionar novas funções virtuais a uma família de classes sem modificar as próprias classes; em vez disso, cria-se uma classe de visitante que implementa todas as especializações apropriadas da função virtual. O visitante toma a referência da instância como entrada e implementa a meta por meio de despacho duplo. ”
O envio duplo é uma necessidade aqui devido à matriz 3x2
Aqui está como será a configuração -
Eu escrevi o exemplo para responder a outra pergunta, o código e sua explicação são mencionados aqui .
fonte
Achei mais fácil nos seguintes links:
Em http://www.remondo.net/visitor-pattern-example-csharp/ , encontrei um exemplo que mostra um exemplo simulado que mostra qual é o benefício do padrão de visitante. Aqui você tem diferentes classes de contêineres para
Pill
:Como você vê acima, você
BilsterPack
contém pares de comprimidos '; portanto, é necessário multiplicar o número de pares por 2. Além disso, você pode perceber que oBottle
usounit
é de tipo de dados diferente e precisa ser convertido.Portanto, no método principal, você pode calcular a contagem de comprimidos usando o seguinte código:
Observe que o código acima viola
Single Responsibility Principle
. Isso significa que você deve alterar o código do método principal se adicionar um novo tipo de contêiner. Também prolongar a troca é uma má prática.Então, introduzindo o seguinte código:
Você mudou a responsabilidade de contar o número de
Pill
s para a classe chamadaPillCountVisitor
(E removemos a instrução switch case). Isso significa que sempre que você precisar adicionar um novo tipo de contêiner de comprimido, você deverá alterar apenas aPillCountVisitor
classe. Observe também que aIVisitor
interface é geral para uso em outros cenários.Adicionando o método Accept à classe de contêiner de comprimidos:
permitimos ao visitante visitar as classes de contêineres de comprimidos.
No final, calculamos a contagem de comprimidos usando o seguinte código:
Isso significa: todo recipiente de pílula permite que o
PillCountVisitor
visitante veja a contagem de pílulas. Ele sabe como contar a sua pílula.No
visitor.Count
tem o valor de comprimidos.Em http://butunclebob.com/ArticleS.UncleBob.IuseVisitor, você vê um cenário real no qual não é possível usar o polimorfismo (a resposta) para seguir o Princípio de responsabilidade única. De fato em:
o
reportQtdHoursAndPay
método é para relatório e representação e isso viola o Princípio de Responsabilidade Única. Portanto, é melhor usar o padrão de visitante para superar o problema.fonte
O envio duplo é apenas uma das razões para usar esse padrão .
Mas observe que é a única maneira de implementar o envio duplo ou mais em idiomas que usa um único paradigma de envio.
Aqui estão os motivos para usar o padrão:
1) Queremos definir novas operações sem alterar o modelo a cada momento, porque o modelo não muda frequentemente, enquanto as operações mudam com frequência.
2) Não queremos unir modelo e comportamento porque queremos ter um modelo reutilizável em vários aplicativos ou queremos ter um modelo extensível que permita que as classes clientes definam seus comportamentos com suas próprias classes.
3) Temos operações comuns que dependem do tipo concreto do modelo, mas não queremos implementar a lógica em cada subclasse, pois isso explodiria a lógica comum em várias classes e, portanto, em vários lugares .
4) Estamos usando um design de modelo de domínio e as classes de modelo da mesma hierarquia executam muitas coisas distintas que poderiam ser reunidas em outro lugar .
5) Precisamos de uma expedição dupla .
Temos variáveis declaradas com tipos de interface e queremos poder processá-las de acordo com o tipo de tempo de execução ... é claro, sem usar
if (myObj instanceof Foo) {}
nenhum truque.A idéia é, por exemplo, passar essas variáveis para métodos que declaram um tipo concreto da interface como parâmetro para aplicar um processamento específico. Essa maneira de fazer não é possível imediatamente, com idiomas, depende de um único despacho, porque o escolhido invocado no tempo de execução depende apenas do tipo de tempo de execução do receptor.
Observe que em Java, o método (assinatura) a ser chamado é escolhido no momento da compilação e depende do tipo declarado dos parâmetros, não do tipo de tempo de execução.
O último ponto que é um motivo para usar o visitante também é uma conseqüência, porque à medida que você implementa o visitante (é claro para idiomas que não suportam o envio múltiplo), você precisa necessariamente introduzir uma implementação de envio duplo.
Observe que a passagem de elementos (iteração) para aplicar o visitante em cada um deles não é um motivo para usar o padrão.
Você usa o padrão porque divide o modelo e o processamento.
E, usando o padrão, você se beneficia, além de uma capacidade de iterador.
Essa capacidade é muito poderosa e vai além da iteração no tipo comum com um método específico, como
accept()
é um método genérico.É um caso de uso especial. Então, vou colocar isso de lado.
Exemplo em Java
Ilustrarei o valor agregado do padrão com um exemplo de xadrez no qual gostaríamos de definir o processamento à medida que o jogador solicita uma peça em movimento.
Sem o uso do padrão de visitante, poderíamos definir comportamentos de movimentação de peças diretamente nas subclasses de peças.
Poderíamos ter, por exemplo, uma
Piece
interface como:Cada subclasse de Piece o implementaria como:
E o mesmo para todas as subclasses de Piece.
Aqui está uma classe de diagrama que ilustra esse design:
Essa abordagem apresenta três desvantagens importantes:
- comportamentos como
performMove()
oucomputeIfKingCheck()
provavelmente usarão lógica comum.Por exemplo, qualquer que seja o concreto
Piece
,performMove()
finalmente definirá a peça atual para um local específico e potencialmente levará a peça oponente.A divisão de comportamentos relacionados em várias classes, em vez de reuni-los, derrota de alguma maneira o padrão de responsabilidade único. Tornando sua manutenção mais difícil.
- processar como
checkMoveValidity()
não deve ser algo que asPiece
subclasses possam ver ou mudar.É o cheque que vai além das ações humanas ou do computador. Essa verificação é realizada em cada ação solicitada por um jogador para garantir que o movimento da peça solicitada seja válido.
Portanto, nem queremos fornecer isso na
Piece
interface.- Em jogos de xadrez desafiadores para desenvolvedores de bot, geralmente o aplicativo fornece uma API padrão (
Piece
interfaces, subclasses, tabuleiro, comportamentos comuns, etc.) e permite que os desenvolvedores aprimorem sua estratégia de bot.Para poder fazer isso, precisamos propor um modelo em que dados e comportamentos não sejam fortemente acoplados nas
Piece
implementações.Então, vamos usar o padrão de visitante!
Temos dois tipos de estrutura:
- as classes de modelo que aceitam serem visitadas (as peças)
- os visitantes que os visitam (operações móveis)
Aqui está um diagrama de classes que ilustra o padrão:
Na parte superior, temos os visitantes e na parte inferior, temos as classes de modelo.
Aqui está a
PieceMovingVisitor
interface (comportamento especificado para cada tipo dePiece
):A peça está definida agora:
Seu método principal é:
Ele fornece o primeiro despacho: uma chamada baseada no
Piece
receptor.Em tempo de compilação, o método é vinculado ao
accept()
método da interface Piece e, em tempo de execução, o método limitado será chamado naPiece
classe de tempo de execução .E é a
accept()
implementação do método que executará um segundo envio.De fato, cada
Piece
subclasse que deseja ser visitada por umPieceMovingVisitor
objeto invoca oPieceMovingVisitor.visit()
método passando como o próprio argumento.Dessa forma, o compilador limita, assim que o tempo de compilação, o tipo do parâmetro declarado com o tipo concreto.
Há o segundo despacho.
Aqui está a
Bishop
subclasse que ilustra que:E aqui um exemplo de uso:
Desvantagens do visitante
O padrão Visitor é um padrão muito poderoso, mas também possui algumas limitações importantes que você deve considerar antes de usá-lo.
1) Risco de reduzir / quebrar o encapsulamento
Em alguns tipos de operação, o padrão de visitante pode reduzir ou interromper o encapsulamento de objetos de domínio.
Por exemplo, como a
MovePerformingVisitor
classe precisa definir as coordenadas da peça real, aPiece
interface deve fornecer uma maneira de fazer isso:A responsabilidade das
Piece
alterações de coordenadas está agora aberta a outras classes além dasPiece
subclasses.Mover o processamento executado pelo visitante nas
Piece
subclasses também não é uma opção.De fato, criará outro problema, pois
Piece.accept()
aceita qualquer implementação de visitante. Ele não sabe o que o visitante executa e, portanto, não faz ideia sobre se e como alterar o estado da Peça.Uma maneira de identificar o visitante seria realizar um pós-processamento de
Piece.accept()
acordo com a implementação do visitante. Seria uma péssima idéia, pois criaria um alto acoplamento entre as implementações do Visitor e as subclasses de Piece e, além disso, provavelmente exigiria o uso de truques comogetClass()
,instanceof
ou qualquer marcador que identifique a implementação do Visitor.2) Requisito para alterar o modelo
Ao contrário de outros padrões de design comportamental,
Decorator
por exemplo, o padrão de visitantes é intrusivo.De fato, precisamos modificar a classe do receptor inicial para fornecer um
accept()
método para aceitar a visita.Não tivemos nenhum problema para
Piece
e suas subclasses, pois essas são nossas classes .Nas aulas integradas ou de terceiros, as coisas não são tão fáceis.
Precisamos envoltório ou herdar (se podemos)-los para adicionar o
accept()
método.3) Indirecionamentos
O padrão cria múltiplos indiretos.
O despacho duplo significa duas invocações em vez de uma única:
E poderíamos ter indiretos adicionais à medida que o visitante altera o estado do objeto visitado.
Pode parecer um ciclo:
fonte
Cay Horstmann tem um ótimo exemplo de onde aplicar o Visitor em seu livro OO Design and patterns . Ele resume o problema:
A razão pela qual não é fácil é porque as operações são adicionadas nas próprias classes de estrutura. Por exemplo, imagine que você tenha um sistema de arquivos:
Aqui estão algumas operações (funcionalidades) que podemos querer implementar com esta estrutura:
Você pode adicionar funções a cada classe no FileSystem para implementar as operações (e as pessoas fizeram isso no passado, pois é muito óbvio como fazê-lo). O problema é que sempre que você adiciona uma nova funcionalidade (a linha "etc." acima), pode ser necessário adicionar mais e mais métodos às classes de estrutura. Em algum momento, após algumas operações adicionadas ao seu software, os métodos nessas classes não fazem mais sentido em termos de coesão funcional das classes. Por exemplo, você
FileNode
possui um método que tem comocalculateFileColorForFunctionABC()
objetivo implementar a funcionalidade de visualização mais recente no sistema de arquivos.O Visitor Visitor (como muitos padrões de design) nasceu da dor e do sofrimento dos desenvolvedores que sabiam que havia uma maneira melhor de permitir que seu código fosse alterado sem exigir muitas alterações em todos os lugares e também respeitando os bons princípios de design (alta coesão, baixo acoplamento ) É minha opinião que é difícil entender a utilidade de muitos padrões até que você sinta essa dor. Explicar a dor (como tentamos fazer acima com as funcionalidades "etc." que são adicionadas) ocupa espaço na explicação e é uma distração. Compreender padrões é difícil por esse motivo.
O Visitor nos permite dissociar as funcionalidades da estrutura de dados (por exemplo,
FileSystemNodes
) das próprias estruturas de dados. O padrão permite que o design respeite a coesão - as classes de estrutura de dados são mais simples (possuem menos métodos) e também as funcionalidades são encapsuladas emVisitor
implementações. Isso é feito por meio de despacho duplo (que é a parte complicada do padrão): usandoaccept()
métodos nas classes de estrutura evisitX()
métodos nas classes Visitor (a funcionalidade):Essa estrutura nos permite adicionar novas funcionalidades que funcionam na estrutura como Visitantes concretos (sem alterar as classes da estrutura).
Por exemplo, um
PrintNameVisitor
que implementa a funcionalidade de listagem de diretórios e umPrintSizeVisitor
que implementa a versão com o tamanho. Podemos imaginar um dia ter um 'ExportXMLVisitor` que gera os dados em XML, ou outro visitante que gera-lo em JSON, etc. Poderíamos até ter um visitante que exibe minha árvore de diretórios usando uma linguagem gráfica como DOT , para ser visualizado com outro programa.Como nota final: a complexidade do Visitor com seu envio duplo significa que é mais difícil entender, codificar e depurar. Em suma, possui um alto fator de nerd e segue o princípio do KISS. Em uma pesquisa feita por pesquisadores, o Visitor mostrou ser um padrão controverso (não havia consenso sobre sua utilidade). Algumas experiências até mostraram que não tornava o código mais fácil de manter.
fonte
Na minha opinião, a quantidade de trabalho para adicionar uma nova operação é mais ou menos a mesma usando
Visitor Pattern
ou modificando diretamente a estrutura de cada elemento. Além disso, se eu adicionar uma nova classe de elemento, por exemploCow
, a interface de Operação será afetada e isso se propaga a toda a classe de elementos existente, exigindo, portanto, a recompilação de todas as classes de elemento. Então qual é o ponto?fonte
rootElement.visit (node) -> node.collapse()
. Com o visitante, cada nó implementa a travessia do gráfico para todos os seus filhos, para que você termine.levelsRemaining
contador como parâmetro. Diminua-o antes de chamar o próximo nível de filhos. Dentro do seu visitanteif(levelsRemaining == 0) return
.Padrão de Visitante como a mesma implementação subterrânea da programação do Aspect Object.
Por exemplo, se você definir uma nova operação sem alterar as classes dos elementos nos quais opera
fonte
Descrição rápida do padrão de visitante. Todas as classes que requerem modificação devem implementar o método 'accept'. Os clientes chamam esse método de aceitação para executar alguma nova ação nessa família de classes, estendendo sua funcionalidade. Os clientes podem usar esse método de aceitação para executar uma ampla variedade de novas ações, passando uma classe de visitante diferente para cada ação específica. Uma classe de visitante contém vários métodos de visita substituídos que definem como realizar a mesma ação específica para todas as classes da família. Esses métodos de visita passam por uma instância na qual trabalhar.
Quando você pode considerar usá-lo
fonte
Eu não entendi esse padrão até encontrar o artigo do tio bob e ler os comentários. Considere o seguinte código:
Embora pareça bom, pois confirma a responsabilidade única , viola o princípio de aberto / fechado . Cada vez que você tiver um novo tipo de funcionário, será necessário adicionar se com verificação de tipo. E se você não quiser, nunca saberá isso em tempo de compilação.
Com o padrão de visitantes, você pode tornar seu código mais limpo, uma vez que não viola o princípio de aberto / fechado e não viola a responsabilidade única. E se você esquecer de implementar a visita, ela não será compilada:
A mágica é que, embora
v.Visit(this)
pareça o mesmo, na verdade é diferente, pois chama sobrecargas diferentes de visitantes.fonte
Com base na excelente resposta de @Federico A. Ramponi.
Imagine que você tem esta hierarquia:
O que acontecerá se você precisar adicionar um método "Walk" aqui? Isso será doloroso para todo o design.
Ao mesmo tempo, adicionar o método "Caminhar" gera novas perguntas. E quanto a "Comer" ou "Dormir"? Devemos realmente adicionar um novo método à hierarquia Animal para cada nova ação ou operação que desejamos adicionar? Isso é feio e mais importante: nunca conseguiremos fechar a interface Animal. Portanto, com o padrão de visitante, podemos adicionar um novo método à hierarquia sem modificar a hierarquia!
Portanto, basta verificar e executar este exemplo de C #:
fonte
Dog
, bem comoCat
. Você poderia tê-los feito na classe base para que eles sejam herdados ou escolha um exemplo adequado.Visitante
Estrutura do visitante:
Use o padrão Visitor se:
Mesmo que o padrão Visitor ofereça flexibilidade para adicionar nova operação sem alterar o código existente no Object, essa flexibilidade é uma desvantagem.
Se um novo objeto Visitável foi adicionado, ele exige alterações de código nas classes Visitor & ConcreteVisitor . Há uma solução alternativa para resolver esse problema: Use a reflexão, que terá impacto no desempenho.
Fragmento de código:
Explicação:
Visitable
(Element
) é uma interface e esse método de interface deve ser adicionado a um conjunto de classes.Visitor
é uma interface, que contém métodos para executar uma operação emVisitable
elementos.GameVisitor
é uma classe que implementaVisitor
interface (ConcreteVisitor
).Visitable
elemento aceitaVisitor
e chama um método relevante deVisitor
interface.Game
comoElement
e jogos concretosChess,Checkers and Ludo
comoConcreteElements
.No exemplo acima,
Chess, Checkers and Ludo
existem três jogos diferentes (eVisitable
classes). Em um belo dia, encontrei um cenário para registrar estatísticas de cada jogo. Portanto, sem modificar a classe individual para implementar a funcionalidade estatística, você pode centralizar essa responsabilidade naGameVisitor
aula, o que faz o truque para você sem modificar a estrutura de cada jogo.resultado:
Referir-se
artigo oodesign
artigo de criação
para mais detalhes
Decorador
Mensagens relacionadas:
Padrão Decorador para IO
Quando usar o padrão do decorador?
fonte
Eu realmente gosto da descrição e do exemplo de http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html .
fonte
Embora tenha entendido o como e quando, nunca entendi o porquê. Caso ajude qualquer pessoa com experiência em uma linguagem como C ++, você deve ler isso com muito cuidado.
Para os preguiçosos, usamos o padrão de visitantes porque "enquanto as funções virtuais são despachadas dinamicamente em C ++, a sobrecarga de funções é feita estaticamente" .
Ou, de outra forma, verifique se CollideWith (ApolloSpacecraft &) é chamado quando você passa uma referência da SpaceShip que está realmente vinculada a um objeto ApolloSpacecraft.
fonte
Obrigado pela incrível explicação de @Federico A. Ramponi , acabei de fazer isso na versão java . Espero que possa ser útil.
Também, como @Konrad Rudolph apontou, é na verdade um despacho duplo usando duas instâncias concretas juntas para determinar os métodos de tempo de execução.
Portanto, na verdade, não há necessidade de criar uma interface comum para o executor da operação , desde que tenhamos a interface da operação definida corretamente.
Como você espera, uma interface comum nos trará mais clareza, embora na verdade não seja a parte essencial desse padrão.
fonte
sua pergunta é quando saber:
Eu não primeiro código com padrão de visitante. Código padrão e aguardo a necessidade de ocorrer e refatorar. digamos que você tenha vários sistemas de pagamento que você instalou um de cada vez. No momento da finalização da compra, você pode ter muitas condições if (ou instanceOf), por exemplo:
Agora imagine que eu tinha 10 métodos de pagamento, fica meio feio. Então, quando você vê esse tipo de padrão, o visitante aparece para separar tudo isso e você acaba chamando algo assim depois:
Você pode ver como implementá-lo a partir do número de exemplos aqui, estou apenas mostrando um caso de usuário.
fonte