Usando padrão de visitante com hierarquia de objetos grandes


12

Contexto

Eu tenho usado com uma hierarquia de objetos (uma árvore de expressão) um padrão de visitante "pseudo" (pseudo, pois ele não usa expedição dupla):

 public interface MyInterface
 {
      void Accept(SomeClass operationClass);
 }

 public class MyImpl : MyInterface 
 {
      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }
 }

Esse design era, no entanto questionável, bastante confortável, pois o número de implementações do MyInterface é significativo (~ 50 ou mais) e eu não precisava adicionar operações extras.

Cada implementação é única (é uma expressão ou operador diferente) e algumas são compostas (por exemplo, nós do operador que conterão outros nós do operador / folha).

No momento, o Traversal é executado chamando a operação Accept no nó raiz da árvore, que por sua vez chama Accept em cada um dos nós filhos, que por sua vez ... e assim por diante ...

Mas chegou a hora de adicionar uma nova operação , como uma bonita impressão:

 public class MyImpl : MyInterface 
 {
      // Property does not come from MyInterface
      public string SomeProperty { get; set; }

      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }

      public void Accept(SomePrettyPrinter printer)
      {
           printer.PrettyPrint(this.SomeProperty);
      }
 }    

Basicamente, vejo duas opções:

  • Manter o mesmo design, adicionando um novo método para minha operação a cada classe derivada, às custas da manutenção (não uma opção, IMHO)
  • Use o padrão Visitante "true", à custa da extensibilidade (não uma opção, pois espero ter mais implementações ao longo do caminho ...), com mais de 50 sobrecargas do método Visit, cada uma correspondendo a uma implementação específica ?

Questão

Você recomendaria usar o padrão Visitor? Existe algum outro padrão que possa ajudar a resolver esse problema?


1
Talvez uma cadeia de decoradores seja mais apropriada?
MattDavey

algumas perguntas: como essas implementações diferem? qual é a estrutura da hierarquia? e é sempre a mesma estrutura? você sempre precisa atravessar a estrutura na mesma ordem?
jk.

@ MattDavey: então você recomendaria ter um decorador por implementação e operação?
562

2
@ T.Fabre é difícil dizer. Existem mais de 50 implementadores de MyInterface.. todas essas classes têm uma implementação exclusiva de DoSomethinge DoSomethingElse? Não vejo onde sua classe visitante realmente atravessa a hierarquia - que mais parece um facadeno momento ..
MattDavey

também qual versão do C # é essa. você tem lambdas? ou linq? à sua disposição
jk.

Respostas:


13

Eu tenho usado o padrão de visitantes para representar árvores de expressão ao longo de mais de 10 anos em seis projetos de larga escala em três linguagens de programação, e estou muito satisfeito com o resultado. Encontrei algumas coisas que tornaram a aplicação do padrão muito mais fácil:

Não use sobrecargas na interface do visitante

Coloque o tipo no nome do método, ou seja, use

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
}

ao invés de

IExpressionVisitor {
    void Visit(IPrimitiveExpression expr);
    void Visit(ICompositeExpression expr);
}

Adicione um método "pegar desconhecido" à sua interface de visitante.

Isso tornaria possível para usuários que não podem modificar seu código:

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
    void VisitExpression(IExpression expr);
};

Isso permitiria que eles criassem suas próprias implementações IExpressione IVisitorque "entendessem" suas expressões usando informações de tipo de tempo de execução na implementação de seu VisitExpressionmétodo catch-all .

Forneça uma implementação IVisitorpadrão da interface " faça nada"

Isso permitiria que os usuários que precisam lidar com um subconjunto de tipos de expressão desenvolvessem seus visitantes mais rapidamente e tornassem o código imune a você adicionando mais métodos IVisitor. Por exemplo, escrever um visitante que colha todos os nomes de variáveis ​​de suas expressões torna-se uma tarefa fácil, e o código não será interrompido, mesmo se você adicionar vários tipos de expressões IVisitormais tarde.


2
Você pode esclarecer por que você diz Do not use overloads in the interface of the visitor?
9118 Steven Evers

1
Você pode explicar por que você não recomenda o uso de sobrecargas? Eu li em algum lugar (no oodesign.com, na verdade) que realmente não importa se uso sobrecargas ou não. Existe algum motivo específico para você preferir esse design?
562 Fabre:

2
@ T.Fabre Não importa em termos de velocidade, mas em termos de legibilidade. A resolução do método em duas das três linguagens em que eu implementei isso ( Java e C #) exige uma etapa de tempo de execução para escolher entre as possíveis sobrecargas, tornando um pouco mais difícil a leitura do código com grande número de sobrecargas. A refatoração do código também se torna mais fácil, porque escolher o método que você deseja modificar se torna uma tarefa trivial.
dasblinkenlight

@SnOrfus Por favor, veja minha resposta para T.Fabre acima.
dasblinkenlight

O @dasblinkenlight C # agora oferece dinâmico para permitir que o tempo de execução decida qual método sobrecarregado deve ser usado (não no tempo de compilação). Ainda existe alguma razão para não usar sobrecarga?
Tintenfiisch
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.