26 de agosto de 2010

Design Patterns com Delphi : Interpreter - parte II

Do ponto de vista da construção de classes e seus relacionamentos, a implementação do Design Pattern comportamental Interpreter (introduzido no post anterior) não apresenta grandes desafios.

Como podemos ver no diagrama reproduzido abaixo, há apenas heranças simples e classes acessando normalmente os membros umas das outras.
Diagrama UML para o padrão Interpreter
Para começar, precisamos definir uma classe abstrata para representar a base de todas as expressões. É nessa classe que estabelecemos a interface para a avaliação de expressões, isto é, o nome da função de avaliação, seus parâmetros e tipo de retorno. Essa definição é que torna a classe abstrata, forçando as heranças a providenciarem o funcionamento correto inerente a cada uma delas.

No nosso exemplo, a classe base é a TWExpressaoAbstrata, cuja função para avaliação das expressões chama-se Calcular, que calculará o saldo disponível do item informado no parâmetro. Segue a declaração em Delphi.
{ Expressão Abstrata }
TWExpressaoAbstrata = class
public
function Calcular (AItem: TWItem) : double;virtual;abstract;
end;

{ Expressão Não-Terminal }
TWExpressaoSoma = class(TWExpressaoAbstrata)
protected
_Expr1, _Expr2: TWExpressaoAbstrata;

public
constructor Create (AExpr1, AExpr2: TWExpressaoAbstrata);
destructor Destroy;override;

function Calcular (AItem: TWItem) : double;override;
end;

{ Expressão Terminal }
TWExpressaoSaldo = class(TWExpressaoAbstrata)
public
function Calcular (AItem: TWItem) : double;override;
end;

No trecho acima aparecem ainda as declarações de duas classes que herdam da TWExpressaoAbstrata e que são exemplos de codificação para os tipos de expressões existentes. Cada uma delas fornece uma implementação própria da função Calcular, dependendo do objetivo da classe. Uma delas é a TWExpressaoSaldo, implementando uma expressão terminal (aquela que não depende de outra expressão para poder ser avaliada). A outra classe é a TWExpressaoSoma, que implementa a expressão não-terminal relativa à operação de soma. Esse tipo de expressão depende de outras expressões para ter seu valor calculado, tornando necessária a presença das variáveis internas _Expr1 e _Expr2. Como essas variáveis internas são declaradas com o tipo base abstrato TWExpressaoAbstrata, elas podem receber qualquer tipo de expressão, tanto as terminais quanto outras não-terminais. Vejas a implementação das funções dessas classes :
{ Expressão não-terminal para a operação de Soma }
constructor TWExpressaoSoma.Create (AExpr1, AExpr2: TWExpressaoAbstrata);
begin
_Expr1 := AExpr1;
_Expr2 := AExpr2;
end;

destructor TWExpressaoSoma.Destroy;
begin
FreeAndNil (_Expr1);
FreeAndNil (_Expr2);

inherited;
end;

function TWExpressaoSoma.Calcular (AItem: TWItem) : double;
begin
Result := _Expr1.Calcular(AItem) + _Expr2.Calcular(AItem);
end;

{ Expressão terminal que apenas retorna o saldo físico atual do item }
function TWExpressaoSaldo.Calcular (AItem: TWItem) : double;
begin
Result := AItem._Saldo;
end;

Veja que a função Calcular na operação de soma exige que ambas as instâncias de expressões internas estejam presentes e sejam válidas, já que ambas têm que ser avaliadas antes de terem seus valores somados para produzir o resultado esperado.

Normalmente, seu programa terá referência apenas à expressão de nível mais alto - as demais, se houver, serão instâncias internas a esta de nível mais alto. Isso significa que se esta expressão é não-terminal, então ela é composta por outras expressões às quais a aplicação não tem acesso direto. É por isso que expressões não-terminais têm a responsabilidade de liberar a memória usada pelas expressões internas que as compõem, conforme vemos no destrutor mostrado no trecho de código acima.

A classe TWItem não possui nada de especial, podendo ser até mesmo uma estrutura simples, se for necessário. A única coisa a ressaltar é que as expressões definidas neste exemplo usam o TWItem como contexto, acarretando a obrigatoriedade de que essas expressões conheçam integralmente a definição do item. Com isso, as expressões ficam amarradas a esse contexto envolvendo itens.

No próximo post, eu mostro como codificar uma análise sintática básica a partir de uma expressão textual, criando no processo as instâncias de classes de expressão que resultarão numa única instância passível de ser avaliada com vários contextos distintos.

Nenhum comentário :

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.