C# - Padrão Comportamental Gof - Interpreter
Neste artigo vou apresentar o padrão comportamental Gof Interpreter. |
Segundo a
definição da Gang Of Four(GoF), dada uma linguagem, o padrão
Interpreter define uma representação para sua gramática junto com um
intérprete que usa a representação para interpretar frases na linguagem.
Este padrão é usado para avaliar/interpretar as instruções escritas em uma gramática de linguagem ou notações. Ele envolve a implementação de uma interface de expressão que diz para interpretar um contexto específico.
Assim o padrão Interpreter fornece uma maneira de
avaliar a gramática ou expressão da linguagem e pode ser usado em análise SQL,
mecanismo de processamento de símbolos, em compiladores, em analisadores, etc.
Este padrão é um
pouco diferente dos outros padrões de projeto. Formalmente, o padrão trata
de lidar com linguagens com base em um conjunto de regras.
Assim, temos uma linguagem - qualquer linguagem - e suas regras, ou seja, a
gramática. Temos também um intérprete que usa o conjunto de regras para
interpretar as sentenças da linguagem.
O uso deste padrão é específico a determinados contextos.
Interpreter : Exemplo de definição
Vamos entender o padrão Interpreter com um exemplo.
Na figura temos , o Contexto que representa o valor que desejamos interpretar. Aqui o valor do contexto é a data atual:
A seguir temos a
Gramática e a Expressão das datas. Aqui temos diferentes tipos de
expressão de datas usando os formatos :
- MM-DD-YYYY
- DD-MM-YYYY
- YYYY-MM-DD
- DD-YYYY
E o
Interpreter :
Suponha que você deseja expressar a data no formato
MM-DD-YYYY.
Então você precisa passar o valor do contexto e a expressão do formato da data
que você deseja para o Interpreter que vai
converter o valor do contexto no formato da data que você passou.
E se você quiser expressar a data em outros formatos basta repetir o
procedimento.
Assim o Interpreter contem a lógica ou gramática
para converter o objeto Contexto para um formato
especifico usando uma expressão.
E assim que atua o padrão Interpreter.
Diagrama UML
O diagrama UML do padrão Interpreter segundo o Gof apresenta os seguintes participantes
1- Context - Esta é uma classe que contém os dados
que queremos interpretar. Ela contém informações (entrada e saída), que são
usadas pelo intérprete.
2- AbstractExpression - È uma interface que define
o método de interpretação que deve ser implementado pelas subclasses. Este
método usa o objeto de contexto como parâmetro e este objeto de contexto contém
os dados que queremos interpretar.
E aqui que definimos a operação Interpretar, que deve ser implementada por cada
subclasse.
3- TerminalExpression - É uma classe concreta que
implementa a interface AbstractExpression. Ela
representa elementos na gramática que não podem ser substituídos, como símbolos.
4- NonTerminalExpression - É uma classe concreta
que implementa a interface AbstractExpression. Ela
representa elementos que serão substituídos durante a avaliação, como variáveis
ou mesmo regras.
Esta é a classe que implementa a expressão. Isso pode ter outras instâncias do
Expression.
5- Client - É uma classe que constrói a árvore de
sintaxe abstrata para um conjunto de instruções na gramática fornecida.
Esta árvore é construída com a ajuda de instâncias das classes
NonTerminalExpression e TerminalExpression.
Essa é a classe que cria a árvore de sintaxe abstrata para um conjunto de
instruções na gramática especificada. Essa árvore é criada com a ajuda de
instâncias das classes NonTerminalExpression e
TerminalExpression.
Quando podemos usar o padrão
Podemos usar o padrão Interpreter nos seguintes cenários :
- Pode ser usado
quando houver uma linguagem para interpretar. Funciona melhor quando :
- Gramática for simples
- A eficiência não for uma preocupação crítica
- Quando puder representar sentenças da linguagem como árvores sintáticas
abstratas
Vantagens do padrão
Como vantagens deste padrão temos que :
- É fácil mudar e
estender a gramática. Como o padrão usa classes para representar regras
gramaticais, você pode usar herança para alterar ou estender a gramática.
- As expressões existentes podem ser modificadas de forma incremental e as novas
expressões podem ser definidas como variações das antigas.
- Implementar a
gramática também é fácil.
Desvantagem
Como desvantagens
deste padrão temos que :
- Gramáticas complexas são difíceis de manter. O padrão
Interpreter define pelo menos uma classe para cada regra da gramática.
Desta forma, gramáticas contendo muitas regras podem ser difíceis de gerenciar e
manter.
Aplicação prática do padrão
Como exemplo de
implementação vamos converter a informação de data e hora em um formato
especifico usando o padrão Interpreter.
Para fazer isso vamos definir diferentes tipos de gramática. Na figura
temos um formato de data :
E vamos definir uma classe para cada tipo de gramática.
- A classe
ExpressaMes para o Mes : Mes -> ExpressaoMes
- A classe ExpressaDia para o Dia : Dia -> ExpressaoDia
- A classe ExpressaAno para o Ano : Ano -> ExpressaoAno
- A classe Separador para o Separador usado na data
Usando esta gramática podemos criar qualquer tipo de formato de data. Aqui
o problema a ser resolvido é nosso domínio que será representando no contexto
e o código de cada classe representa a regra de gramática que vamos usar.
Implementação prática
Levando em conta este cenário vamos implementar o padrão Command usando uma aplicação Console .NET Core (.NET 5.0) criada no VS 2019 Community.
A seguir temos o diagrama de classes obtido a partir do VS 2019 na implementação do padrão:
Podemos identificar as seguinte classes :
-
Context - Classe que define a data que desejamos
interpretar;
- AbstractExpression - Interface que
define o método que será implementado pelas classes filhas;
- ExpressaoDia, ExpressaoMes e ExpressaoAno e Separador :
Implementam a interface AbstractExpression;
- Program - Client - Usa a implementação;
A seguir temos o código usado na implementação:
1- A classe Context
public class Context { public string Expressao { get; set; } public DateTime Data { get; set; } public Context(DateTime data) { Data = data; } } |
2- A Interface IAbstractExpression
public
interface IAbstractExpression { void Avaliar(Context context); } |
3- Classe ExpressaoDia
public class
ExpressaoDia : IAbstractExpression { public void Avaliar(Context context) { string expressao = context.Expressao; context.Expressao = expressao.Replace("DD", context.Data.Day.ToString()); } } |
4- Classe ExpressaoMes
public class ExpressaoMes : IAbstractExpression { public void Avaliar(Context context) { string expressao = context.Expressao; context.Expressao = expressao.Replace("MM", context.Data.Month.ToString()); } } |
5- Classe ExpressaoAno
|
7- Classe Separador
public
class Separador : IAbstractExpression { public void Avaliar(Context context) { string espressao = context.Expressao; context.Expressao = espressao.Replace(" ", "-"); } } |
7- Program
|
A execução do projeto irá apresentar o seguinte resultado:
Temos assim um
exemplo básico de implementação do padrão Interpreter.
Você pode encontrar algumas semelhanças entre os padrões
Command e Interpreter. O padrão Command diz sobre a conversão das
ações em comando para fins de registro ou desfazer. Aqui, os comandos são
objetos, mas para o padrão Interpreter, os comandos são sentenças.
O padrão Command pode ser usado para objetos
persistentes, serializando a lista de comandos e os resultados em arquivos
grandes comparados com o padrão Interpreter.
O padrão Interpreter fornece uma abordagem mais
fácil no tempo de execução, mas tem o custo de desenvolver um interpretador.
Pegue o código do projeto aqui : Interpreter1.zip
"Em Deus louvarei
a sua palavra, em Deus pus a minha confiança; não temerei o que me possa fazer a
carne."
Salmos 56:4
Referências:
NET - Unit of Work - Padrão Unidade de ...
NET - O padrão de projeto Decorator
NET - Padrão de Projeto Builder
C# - O Padrão Strategy (revisitado)
NET - O padrão de projeto Command
NET - Apresentando o padrão Repository