Quando devo usar o Padrão de design do visitante? [fechadas]

315

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.

George Mauer
fonte
7
Finalmente consegui depois de ler este artigo de Jermey Miller no meu blackberry enquanto esperava no lobby por duas horas. É longo, mas fornece uma explicação maravilhosa de despacho duplo, visitante e composto, e o que você pode fazer com eles.
George Mauer
1
Aqui está um bom artigo: codeproject.com/Articles/186185/Visitor-Design-Pattern
Seyed Morteza Mousavi
3
Padrão de visitante? Qual? O ponto é: há muitos mal-entendidos e pura confusão em torno desse padrão de design. Eu escrevi e artigo que esperamos que põe alguma ordem a este caos: rgomes-info.blogspot.co.uk/2013/01/...
Richard Gomes
Quando você deseja ter objetos de função nos tipos de dados da união, precisará de um padrão de visitante. Você pode se perguntar o que objetos de função e tipos de dados de união são, então vale a pena leitura ccs.neu.edu/home/matthias/htdc.html
Wei Qiu
Exemplos aqui e aqui .
precisa saber é o seguinte

Respostas:

315

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

class Animal {  };
class Dog: public Animal {  };
class Cat: public Animal {  };

(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:

class Animal
{ public: virtual void makeSound() = 0; };

class Dog : public Animal
{ public: void makeSound(); };

void Dog::makeSound()
{ std::cout << "woof!\n"; }

class Cat : public Animal
{ public: void makeSound(); };

void Cat::makeSound()
{ std::cout << "meow!\n"; }

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:

class Operation
{
public:
    virtual void hereIsADog(Dog *d) = 0;
    virtual void hereIsACat(Cat *c) = 0;
};

Em seguida, modificamos a hierarquia para aceitar novas operações:

class Animal
{ public: virtual void letsDo(Operation *v) = 0; };

class Dog : public Animal
{ public: void letsDo(Operation *v); };

void Dog::letsDo(Operation *v)
{ v->hereIsADog(this); }

class Cat : public Animal
{ public: void letsDo(Operation *v); };

void Cat::letsDo(Operation *v)
{ v->hereIsACat(this); }

Por fim, implementamos a operação real, sem modificar nem Cat nem Dog :

class Sound : public Operation
{
public:
    void hereIsADog(Dog *d);
    void hereIsACat(Cat *c);
};

void Sound::hereIsADog(Dog *d)
{ std::cout << "woof!\n"; }

void Sound::hereIsACat(Cat *c)
{ std::cout << "meow!\n"; }

Agora você tem uma maneira de adicionar operações sem modificar mais a hierarquia. Aqui está como funciona:

int main()
{
    Cat c;
    Sound theSound;
    c.letsDo(&theSound);
}
Federico A. Ramponi
fonte
19
S.Lott, andar em uma árvore não é realmente o padrão do visitante. (É o "padrão hierárquico de visitantes", que é completamente diferente.) Não há como mostrar o padrão de visitantes do GoF sem o uso de herança ou implementação de interface.
28510 munificent
14
@Knownasilya - Isso não é verdade. O & -Operator fornece o endereço do objeto de som, necessário à interface. letsDo(Operation *v) precisa de um ponteiro.
AquilaRapax
3
por uma questão de clareza, este exemplo de padrão de design do visitante está correto?
21713 godzilla
4
Depois de muito pensar, pergunto-me por que você chamou dois métodos aquiIsADog e hereIsACat, embora você já passe o Dog e o Cat para os métodos. Eu preferiria um simples performTask (Object * obj) e você converter esse objeto na classe Operation. (e em linguagem apoio substituindo, não há necessidade para a moldagem)
Abdalrahman Shatou
6
No seu exemplo "principal" no final: theSound.hereIsACat(c)teria feito o trabalho, como você justifica toda a sobrecarga introduzida pelo padrão? expedição dupla é a justificativa.
franssu
131

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 .

Konrad Rudolph
fonte
3
+1 "não há padrão" - a resposta perfeita. a resposta mais votada prova que muitos programadores de c ++ ainda não perceberam as limitações das funções virtuais sobre o polimorfismo "adhoc" usando um tipo de enumeração e alternância de maiúsculas e minúsculas (da maneira c). Pode ser mais limpo e invisível usar o virtual, mas ainda está limitado ao envio único. Na minha opinião pessoal, essa é a maior falha do c ++.
user3125280
@ user3125280 Agora, li 4/5 artigos e o capítulo Design Patterns sobre o padrão Visitor, e nenhum deles explica a vantagem de usar esse padrão obscuro sobre um stmt de caso ou quando você pode usar um sobre o outro. Thx por pelo menos trazê-lo à tona!
Spinkus
4
@sam eu tenho certeza que eles explicá-lo - é a mesma vantagem que você sempre ficar de polimorfismo subclassificação / runtime sobre switch: switchrí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.
21978 Konrad Rudolph
@KonradRudolph, obrigado por isso. Observando, porém, não é abordado explicitamente em Patterns ou no artigo da Wikipedia, por exemplo. Eu não discordo de você, mas você pode argumentar que há benefícios em usar um case stmt também, por isso é estranho que geralmente não seja contrastado: 1. você não precisa de um método accept () nos objetos de sua coleção. 2. O ~ visitante pode manipular objetos de tipo desconhecido. Portanto, o case stmt parece ser mais adequado para operar em estruturas de objetos com uma coleção mutável de tipos envolvidos. Patterns reconhece que o padrão Visitor não é adequado para esse cenário (p333).
Spinkus
1
O @SamPinkus konrad está no local - é por isso virtualque 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).
user3125280
84

Todos aqui estão corretos, mas acho que não aborda o "quando". Primeiro, em Design Patterns:

O Visitor permite definir uma nova operação sem alterar as classes dos elementos nos quais opera.

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.

Daniel C. Sobral
fonte
Por que eu usaria o padrão de visitante em apenas uma classe utlity? posso chamar minha classe de utilitário assim: AnalyticsManger.visit (someObjectToVisit) vs AnalyticsVisitor.visit (someOjbectToVisit). Qual é a diferença ? ambos fazem separação de preocupação, certo? espero que você possa ajudar.
J2emanue 28/08/19
@ j2emanue Porque o padrão Visitor usa a sobrecarga correta do Visitor em tempo de execução. Enquanto seu código precisa, digite casting para chamar a sobrecarga correta.
Acesso negado
existe um ganho de eficiência com isso? eu acho que evita vazamento é uma boa idéia
j2emanue
@ j2emanue a idéia é escrever um código que esteja em conformidade com o princípio aberto / fechado, não com razões de desempenho. Veja aberto fechado no tio Bob butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
Acesso negado
22

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.]

class TreeNode( object ):
    def __init__( self, name, *children ):
        self.name= name
        self.children= children
    def visit( self, someVisitor ):
        someVisitor.arrivedAt( self )
        someVisitor.down()
        for c in self.children:
            c.visit( someVisitor )
        someVisitor.up()

O visitmétodo aplica um objeto Visitor a cada nó na estrutura. Nesse caso, é um visitante de cima para baixo. Você pode alterar a estrutura do visitmétodo para fazer de baixo para cima ou de alguma outra ordem.

Aqui está uma superclasse para os visitantes. É usado pelo visitmétodo Ele "chega a" cada nó da estrutura. Desde que o visitmétodo chama upe down, o visitante pode acompanhar a profundidade.

class Visitor( object ):
    def __init__( self ):
        self.depth= 0
    def down( self ):
        self.depth += 1
    def up( self ):
        self.depth -= 1
    def arrivedAt( self, aTreeNode ):
        print self.depth, aTreeNode.name

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 um Visitor, dumpNodes.

Em seguida, aplica-se dumpNodesà árvore. O dumpNodeobjeto "visitará" cada nó na árvore.

someTree= TreeNode( "Top", TreeNode("c1"), TreeNode("c2"), TreeNode("c3") )
dumpNodes= Visitor()
someTree.visit( dumpNodes )

O visitalgoritmo TreeNode garantirá que cada TreeNode seja usado como argumento para o arrivedAtmétodo do Visitor .

S.Lott
fonte
8
Como outros já declararam, esse é o "padrão hierárquico de visitantes".
PPC-Coder
1
@ PPC-Coder Qual é a diferença entre 'padrão hierárquico de visitantes' e padrão de visitantes?
Tim Lovell-Smith
3
O padrão hierárquico de visitantes é mais flexível que o padrão clássico de visitantes. Por exemplo, com o padrão hierárquico, você pode rastrear a profundidade da travessia e decidir qual ramificação atravessar ou parar de atravessar todos juntos. O visitante clássico não tem esse conceito e visitará todos os nós.
PPC-Coder
18

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.)

Oddthinking
fonte
Também gostaria da sua opinião sobre o seguinte: Por que eu usaria o padrão de visitante em apenas uma classe de utlity? posso chamar minha classe de utilitário assim: AnalyticsManger.visit (someObjectToVisit) vs AnalyticsVisitor.visit (someOjbectToVisit). Qual é a diferença ? ambos fazem separação de preocupação, certo? espero que você possa ajudar.
J2emanue 28/08/19
@ j2emanue: Eu não entendo a pergunta. Eu sugiro que você a elabore e publique como uma pergunta completa para qualquer um responder.
Oddthinking 28/08
1
i postou uma nova pergunta aqui: stackoverflow.com/questions/52068876/...
j2emanue
14

Há pelo menos três boas razões para usar o Padrão de Visitante:

  1. Reduza a proliferação de código, que é apenas ligeiramente diferente quando as estruturas de dados são alteradas.

  2. Aplique a mesma computação a várias estruturas de dados, sem alterar o código que implementa a computação.

  3. Adicione informações às bibliotecas herdadas sem alterar o código herdado.

Por favor, dê uma olhada em um artigo que escrevi sobre isso .

Richard Gomes
fonte
1
Comentei seu artigo com o maior uso que já vi para visitantes. Pensamentos?
George Mauer 24/13
13

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 -

insira a descrição da imagem aqui insira a descrição da imagem aqui

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 -

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

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.

insira a descrição da imagem aqui

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 - insira a descrição da imagem aqui

Eu escrevi o exemplo para responder a outra pergunta, o código e sua explicação são mencionados aqui .

Kapoor
fonte
9

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:

namespace DesignPatterns
{
    public class BlisterPack
    {
        // Pairs so x2
        public int TabletPairs { get; set; }
    }

    public class Bottle
    {
        // Unsigned
        public uint Items { get; set; }
    }

    public class Jar
    {
        // Signed
        public int Pieces { get; set; }
    }
}

Como você vê acima, você BilsterPackcontém pares de comprimidos '; portanto, é necessário multiplicar o número de pares por 2. Além disso, você pode perceber que o Bottleuso unité 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:

foreach (var item in packageList)
{
    if (item.GetType() == typeof (BlisterPack))
    {
        pillCount += ((BlisterPack) item).TabletPairs * 2;
    }
    else if (item.GetType() == typeof (Bottle))
    {
        pillCount += (int) ((Bottle) item).Items;
    }
    else if (item.GetType() == typeof (Jar))
    {
        pillCount += ((Jar) item).Pieces;
    }
}

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:

public class PillCountVisitor : IVisitor
{
    public int Count { get; private set; }

    #region IVisitor Members

    public void Visit(BlisterPack blisterPack)
    {
        Count += blisterPack.TabletPairs * 2;
    }

    public void Visit(Bottle bottle)
    {
        Count += (int)bottle.Items;
    }

    public void Visit(Jar jar)
    {
        Count += jar.Pieces;
    }

    #endregion
}

Você mudou a responsabilidade de contar o número de Pills para a classe chamada PillCountVisitor(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 a PillCountVisitorclasse. Observe também que a IVisitorinterface é geral para uso em outros cenários.

Adicionando o método Accept à classe de contêiner de comprimidos:

public class BlisterPack : IAcceptor
{
    public int TabletPairs { get; set; }

    #region IAcceptor Members

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }

    #endregion
}

permitimos ao visitante visitar as classes de contêineres de comprimidos.

No final, calculamos a contagem de comprimidos usando o seguinte código:

var visitor = new PillCountVisitor();

foreach (IAcceptor item in packageList)
{
    item.Accept(visitor);
}

Isso significa: todo recipiente de pílula permite que o PillCountVisitorvisitante veja a contagem de pílulas. Ele sabe como contar a sua pílula.

No visitor.Counttem 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:

public class HourlyEmployee extends Employee {
  public String reportQtdHoursAndPay() {
    //generate the line for this hourly employee
  }
}

o reportQtdHoursAndPaymé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.

Seyed Morteza Mousavi
fonte
2
Olá Sayed, você pode editar sua resposta para adicionar as partes que achou mais esclarecedoras. O SO geralmente desencoraja respostas somente para links, pois o objetivo é ser um banco de dados de conhecimento e os links diminuem.
George Mauer
8

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 Pieceinterface como:

public interface Piece{

    boolean checkMoveValidity(Coordinates coord);

    void performMove(Coordinates coord);

    Piece computeIfKingCheck();

}

Cada subclasse de Piece o implementaria como:

public class Pawn implements Piece{

    @Override
    public boolean checkMoveValidity(Coordinates coord) {
        ...
    }

    @Override
    public void performMove(Coordinates coord) {
        ...
    }

    @Override
    public Piece computeIfKingCheck() {
        ...
    }

}

E o mesmo para todas as subclasses de Piece.
Aqui está uma classe de diagrama que ilustra esse design:

[diagrama de classes do modelo

Essa abordagem apresenta três desvantagens importantes:

- comportamentos como performMove()ou computeIfKingCheck()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 as Piecesubclasses 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 Pieceinterface.

- Em jogos de xadrez desafiadores para desenvolvedores de bot, geralmente o aplicativo fornece uma API padrão ( Pieceinterfaces, 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 Pieceimplementaçõ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:

insira a descrição da imagem aqui

Na parte superior, temos os visitantes e na parte inferior, temos as classes de modelo.

Aqui está a PieceMovingVisitorinterface (comportamento especificado para cada tipo de Piece):

public interface PieceMovingVisitor {

    void visitPawn(Pawn pawn);

    void visitKing(King king);

    void visitQueen(Queen queen);

    void visitKnight(Knight knight);

    void visitRook(Rook rook);

    void visitBishop(Bishop bishop);

}

A peça está definida agora:

public interface Piece {

    void accept(PieceMovingVisitor pieceVisitor);

    Coordinates getCoordinates();

    void setCoordinates(Coordinates coordinates);

}

Seu método principal é:

void accept(PieceMovingVisitor pieceVisitor);

Ele fornece o primeiro despacho: uma chamada baseada no Piecereceptor.
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 na Piececlasse de tempo de execução .
E é a accept()implementação do método que executará um segundo envio.

De fato, cada Piecesubclasse que deseja ser visitada por um PieceMovingVisitorobjeto invoca o PieceMovingVisitor.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 Bishopsubclasse que ilustra que:

public class Bishop implements Piece {

    private Coordinates coord;

    public Bishop(Coordinates coord) {
        super(coord);
    }

    @Override
    public void accept(PieceMovingVisitor pieceVisitor) {
        pieceVisitor.visitBishop(this);
    }

    @Override
    public Coordinates getCoordinates() {
        return coordinates;
    }

   @Override
    public void setCoordinates(Coordinates coordinates) {
        this.coordinates = coordinates;
   }

}

E aqui um exemplo de uso:

// 1. Player requests a move for a specific piece
Piece piece = selectPiece();
Coordinates coord = selectCoordinates();

// 2. We check with MoveCheckingVisitor that the request is valid
final MoveCheckingVisitor moveCheckingVisitor = new MoveCheckingVisitor(coord);
piece.accept(moveCheckingVisitor);

// 3. If the move is valid, MovePerformingVisitor performs the move
if (moveCheckingVisitor.isValid()) {
    piece.accept(new MovePerformingVisitor(coord));
}

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, a Pieceinterface deve fornecer uma maneira de fazer isso:

void setCoordinates(Coordinates coordinates);

A responsabilidade das Piecealterações de coordenadas está agora aberta a outras classes além das Piecesubclasses.
Mover o processamento executado pelo visitante nas Piecesubclasses 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 como getClass(), instanceofou 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, Decoratorpor 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 Piecee 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:

call the visited (piece) -> that calls the visitor (pieceMovingVisitor)

E poderíamos ter indiretos adicionais à medida que o visitante altera o estado do objeto visitado.
Pode parecer um ciclo:

call the visited (piece) -> that calls the visitor (pieceMovingVisitor) -> that calls the visited (piece)
davidxxx
fonte
6

Cay Horstmann tem um ótimo exemplo de onde aplicar o Visitor em seu livro OO Design and patterns . Ele resume o problema:

Objetos compostos geralmente têm uma estrutura complexa, composta por elementos individuais. Alguns elementos podem ter novamente elementos filho. ... Uma operação em um elemento visita seus elementos filhos, aplica a operação a eles e combina os resultados. ... No entanto, não é fácil adicionar novas operações a esse design.

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:

Diagrama de classes do FileSystem

Aqui estão algumas operações (funcionalidades) que podemos querer implementar com esta estrutura:

  • Exibir os nomes dos elementos do nó (uma lista de arquivos)
  • Exibir o tamanho calculado dos elementos do nó (em que o tamanho de um diretório inclui o tamanho de todos os seus elementos filhos)
  • etc.

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ê FileNodepossui um método que tem como calculateFileColorForFunctionABC()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 em Visitorimplementações. Isso é feito por meio de despacho duplo (que é a parte complicada do padrão): usando accept()métodos nas classes de estrutura e visitX()métodos nas classes Visitor (a funcionalidade):

Diagrama de classes FileSystem com o Visitor aplicado

Essa estrutura nos permite adicionar novas funcionalidades que funcionam na estrutura como Visitantes concretos (sem alterar as classes da estrutura).

Diagrama de classes FileSystem com o Visitor aplicado

Por exemplo, um PrintNameVisitorque implementa a funcionalidade de listagem de diretórios e um PrintSizeVisitorque 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.

Fuhrmanator
fonte
Acho que a estrutura de diretórios é um bom padrão composto, mas concordo com o seu último parágrafo.
Zar
5

Na minha opinião, a quantidade de trabalho para adicionar uma nova operação é mais ou menos a mesma usando Visitor Patternou modificando diretamente a estrutura de cada elemento. Além disso, se eu adicionar uma nova classe de elemento, por exemplo Cow, 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?

Kaosad
fonte
4
Quase toda vez que eu uso o Visitor é quando você está trabalhando com uma hierarquia de objetos. Considere um menu de árvore aninhada. Você deseja recolher todos os nós. Se você não implementar o visitante, terá que escrever o código transversal do gráfico. Ou com o visitante: 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.
George Mauer
@GeorgeMauer, o conceito de expedição dupla esclareceu a motivação para mim: ou a lógica dependente de tipo está no tipo ou no mundo da dor. A idéia de distribuir a lógica transversal ainda me dá uma pausa. É mais eficiente? É mais sustentável? E se "dobrar para o nível N" for adicionado como um requisito?
Nik.shornikov
@ A eficiência nik.shornikov realmente não deve ser uma preocupação aqui. Em quase qualquer idioma, algumas chamadas de função são indiretas insignificantes. Qualquer coisa além disso é micro-otimização. É mais sustentável? Bem, isto depende. Eu acho que na maioria das vezes é, às vezes não é. Quanto a "dobre para o nível N". Passagem fácil em um levelsRemainingcontador como parâmetro. Diminua-o antes de chamar o próximo nível de filhos. Dentro do seu visitante if(levelsRemaining == 0) return.
George Mauer
1
@GeorgeMauer, concordou totalmente que a eficiência é uma preocupação menor. Mas a manutenção, por exemplo, substituições da assinatura de aceitação, é exatamente o que eu acho que a decisão deve se resumir.
Nik.shornikov
5

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

blendz
fonte
-se de mencionar Programação Aspect Objeto
milesma
5

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

  1. Quando você tem uma família de classes, sabe que precisará adicionar muitas ações novas a todas, mas, por algum motivo, não poderá alterar ou recompilar a família de classes no futuro.
  2. Quando você deseja adicionar uma nova ação e ter essa nova ação totalmente definida em uma classe de visitantes, em vez de se espalhar por várias classes.
  3. Quando o seu chefe diz que você deve produzir uma série de classes que devem fazer algo agora mesmo ! ... mas ninguém sabe exatamente o que é isso.
andrew pate
fonte
4

Eu não entendi esse padrão até encontrar o artigo do tio bob e ler os comentários. Considere o seguinte código:

public class Employee
{
}

public class SalariedEmployee : Employee
{
}

public class HourlyEmployee : Employee
{
}

public class QtdHoursAndPayReport
{
    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        foreach (Employee e in employees)
        {
            if (e is HourlyEmployee he)
                PrintReportLine(he);
            if (e is SalariedEmployee se)
                PrintReportLine(se);
        }
    }

    public void PrintReportLine(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hours");
    }
    public void PrintReportLine(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    }
}

class Program
{
    static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }
}

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:

public abstract class Employee
{
    public abstract void Accept(EmployeeVisitor v);
}

public class SalariedEmployee : Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public class HourlyEmployee:Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public interface EmployeeVisitor
{
    void Visit(HourlyEmployee he);
    void Visit(SalariedEmployee se);
}

public class QtdHoursAndPayReport : EmployeeVisitor
{
    public void Visit(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hourly");
        // generate the line of the report.
    }
    public void Visit(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    } // do nothing

    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        QtdHoursAndPayReport v = new QtdHoursAndPayReport();
        foreach (var emp in employees)
        {
            emp.Accept(v);
        }
    }
}

class Program
{

    public static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }       
}  
}

A mágica é que, embora v.Visit(this)pareça o mesmo, na verdade é diferente, pois chama sobrecargas diferentes de visitantes.

Acesso negado
fonte
Sim, acho especificamente útil ao trabalhar com estruturas de árvores, não apenas listas simples (as listas simples seriam um caso especial de uma árvore). Como você observar, a sua não terrivelmente bagunçado apenas em listas, porém visitante pode ser um salvador como a navegação entre nós se torna mais complexa
George Mauer
3

Com base na excelente resposta de @Federico A. Ramponi.

Imagine que você tem esta hierarquia:

public interface IAnimal
{
    void DoSound();
}

public class Dog : IAnimal
{
    public void DoSound()
    {
        Console.WriteLine("Woof");
    }
}

public class Cat : IAnimal
{
    public void DoSound(IOperation o)
    {
        Console.WriteLine("Meaw");
    }
}

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 #:

using System;
using System.Collections.Generic;

namespace VisitorPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var animals = new List<IAnimal>
            {
                new Cat(), new Cat(), new Dog(), new Cat(), 
                new Dog(), new Dog(), new Cat(), new Dog()
            };

            foreach (var animal in animals)
            {
                animal.DoOperation(new Walk());
                animal.DoOperation(new Sound());
            }

            Console.ReadLine();
        }
    }

    public interface IOperation
    {
        void PerformOperation(Dog dog);
        void PerformOperation(Cat cat);
    }

    public class Walk : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Dog walking");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Cat Walking");
        }
    }

    public class Sound : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Woof");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Meaw");
        }
    }

    public interface IAnimal
    {
        void DoOperation(IOperation o);
    }

    public class Dog : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }

    public class Cat : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }
}
Tomás Escamez
fonte
andar, comer não são exemplos adequados desde que são comuns a ambos Dog, bem como Cat. Você poderia tê-los feito na classe base para que eles sejam herdados ou escolha um exemplo adequado.
Abhinav Gauniyal 19/09/16
sons são tho diferente, boa amostra, mas não tem certeza se ele tem alguma coisa a ver com o padrão do visitante
DAG
3

Visitante

Visitor 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

Estrutura do visitante:

insira a descrição da imagem aqui

Use o padrão Visitor se:

  1. Operações similares devem ser executadas em objetos de diferentes tipos agrupados em uma estrutura
  2. Você precisa executar muitas operações distintas e não relacionadas. Separa Operação dos objetos Estrutura
  3. Novas operações devem ser adicionadas sem alteração na estrutura do objeto
  4. Reúna operações relacionadas em uma única classe em vez de forçá-lo a alterar ou derivar classes
  5. Adicione funções às bibliotecas de classes para as quais você não possui a fonte ou não pode alterar a fonte

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:

import java.util.HashMap;

interface Visitable{
    void accept(Visitor visitor);
}

interface Visitor{
    void logGameStatistics(Chess chess);
    void logGameStatistics(Checkers checkers);
    void logGameStatistics(Ludo ludo);    
}
class GameVisitor implements Visitor{
    public void logGameStatistics(Chess chess){
        System.out.println("Logging Chess statistics: Game Completion duration, number of moves etc..");    
    }
    public void logGameStatistics(Checkers checkers){
        System.out.println("Logging Checkers statistics: Game Completion duration, remaining coins of loser");    
    }
    public void logGameStatistics(Ludo ludo){
        System.out.println("Logging Ludo statistics: Game Completion duration, remaining coins of loser");    
    }
}

abstract class Game{
    // Add game related attributes and methods here
    public Game(){

    }
    public void getNextMove(){};
    public void makeNextMove(){}
    public abstract String getName();
}
class Chess extends Game implements Visitable{
    public String getName(){
        return Chess.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Checkers extends Game implements Visitable{
    public String getName(){
        return Checkers.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Ludo extends Game implements Visitable{
    public String getName(){
        return Ludo.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}

public class VisitorPattern{
    public static void main(String args[]){
        Visitor visitor = new GameVisitor();
        Visitable games[] = { new Chess(),new Checkers(), new Ludo()};
        for (Visitable v : games){
            v.accept(visitor);
        }
    }
}

Explicação:

  1. Visitable( Element) é uma interface e esse método de interface deve ser adicionado a um conjunto de classes.
  2. Visitoré uma interface, que contém métodos para executar uma operação em Visitableelementos.
  3. GameVisitoré uma classe que implementa Visitorinterface ( ConcreteVisitor).
  4. Cada Visitableelemento aceita Visitore chama um método relevante de Visitorinterface.
  5. Você pode tratar Gamecomo Elemente jogos concretos Chess,Checkers and Ludocomo ConcreteElements.

No exemplo acima, Chess, Checkers and Ludoexistem três jogos diferentes (e Visitableclasses). 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 na GameVisitoraula, o que faz o truque para você sem modificar a estrutura de cada jogo.

resultado:

Logging Chess statistics: Game Completion duration, number of moves etc..
Logging Checkers statistics: Game Completion duration, remaining coins of loser
Logging Ludo statistics: Game Completion duration, remaining coins of loser

Referir-se

artigo oodesign

artigo de criação

para mais detalhes

Decorador

O padrão permite que o comportamento seja adicionado a um objeto individual, estaticamente ou dinamicamente, sem afetar o comportamento de outros objetos da mesma classe

Mensagens relacionadas:

Padrão Decorador para IO

Quando usar o padrão do decorador?

Ravindra babu
fonte
2

Eu realmente gosto da descrição e do exemplo de http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html .

A suposição é que você tem uma hierarquia de classe primária que é fixa; talvez seja de outro fornecedor e você não possa fazer alterações nessa hierarquia. No entanto, sua intenção é que você queira adicionar novos métodos polimórficos a essa hierarquia, o que significa que normalmente você precisará adicionar algo à interface da classe base. Portanto, o dilema é que você precisa adicionar métodos à classe base, mas não pode tocar na classe base. Como você contorna isso?

O padrão de design que resolve esse tipo de problema é chamado de “visitante” (o último no livro Design Patterns) e se baseia no esquema de despacho duplo mostrado na última seção.

O padrão de visitante permite estender a interface do tipo primário, criando uma hierarquia de classe separada do tipo Visitor para virtualizar as operações executadas no tipo primário. Os objetos do tipo primário simplesmente "aceitam" o visitante e, em seguida, chamam a função de membro vinculada dinamicamente ao visitante.

wojcikstefan
fonte
Embora tecnicamente o padrão Visitor seja realmente apenas uma expedição dupla básica do exemplo deles. Eu diria que a utilidade não é particularmente visível apenas disso.
George Mauer
1

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.

class SpaceShip {};
class ApolloSpacecraft : public SpaceShip {};
class ExplodingAsteroid : public Asteroid {
public:
  virtual void CollideWith(SpaceShip&) {
    cout << "ExplodingAsteroid hit a SpaceShip" << endl;
  }
  virtual void CollideWith(ApolloSpacecraft&) {
    cout << "ExplodingAsteroid hit an ApolloSpacecraft" << endl;
  }
}
Carl
fonte
2
O uso de despacho dinâmico no padrão de visitante me deixa completamente perplexo. Os usos sugeridos do padrão descrevem ramificações que podem ser feitas em tempo de compilação. Esses casos aparentemente seriam melhores com um modelo de função.
Praxeolitic
0

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.

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showTheHobby(food);
        Katherine katherine = new Katherine();
        katherine.presentHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void embed(Katherine katherine);
}


class Hearen {
    String name = "Hearen";
    void showTheHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine {
    String name = "Katherine";
    void presentHobby(Hobby hobby) {
        hobby.embed(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void embed(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

Como você espera, uma interface comum nos trará mais clareza, embora na verdade não seja a parte essencial desse padrão.

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showHobby(food);
        Katherine katherine = new Katherine();
        katherine.showHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void insert(Katherine katherine);
}

abstract class Person {
    String name;
    protected Person(String n) {
        this.name = n;
    }
    abstract void showHobby(Hobby hobby);
}

class Hearen extends  Person {
    public Hearen() {
        super("Hearen");
    }
    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine extends Person {
    public Katherine() {
        super("Katherine");
    }

    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void insert(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}
Hearen
fonte
0

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:

//psuedo code
    if(payPal) 
    do paypal checkout 
    if(stripe)
    do strip stuff checkout
    if(payoneer)
    do payoneer checkout

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:

new PaymentCheckoutVistor(paymentType).visit()

Você pode ver como implementá-lo a partir do número de exemplos aqui, estou apenas mostrando um caso de usuário.

j2emanue
fonte