A implementação das relações expostas no diagrama não é especialmente complexa. Ela envolve apenas conceitos comuns da programação orientada a objetos, como herança e polimorfismo.
O primeiro passo é declarar uma classe abstrata (ou interface, se preferir), definindo nela as linhas mestras que deverão ser respeitadas por todos as implementações reais do algoritmo em questão. Isto é, essa classe introduz funções que determinam a estratégia para execução da tarefa, impondo-a a todas as classes que queiram atuar como um algoritmo alternativo para a tarefa - daí o nome do padrão ser Strategy:
TWTitulo = class;
TWPagamento=class
public
function efetuaPgto (ATitulo: TWTitulo) : integer;virtual;abstract;
end;
Nesse exemplo, há uma única função abstrata que obrigatoriamente terá que existir em cada nova implementação do algoritmo. Essa função aceita como parâmetro uma instância da classe Context da solução, papel encarnado aqui pelo título a pagar (TWTitulo). Com isso, qualquer algoritmo que adote a estratégia definida tem acesso às informações relevantes do título, bem como a suas operações. Permitir essa comunicação é imprescindível para que a tarefa (o pagamento do título) possa ser concretizada.TWPagamento=class
public
function efetuaPgto (ATitulo: TWTitulo) : integer;virtual;abstract;
end;
Agora que temos a interface estabelecida, podemos adicionar os diferentes algoritmos previstos no diagrama, criando cada um deles como uma herança simples que forneça código para a função abstrata da interface:
TWContaBancaria = class;
TWPgtoBoleto=class(TWPagamento)
{ ... }
public
function efetuaPgto (ATitulo: TWTitulo) : integer;override;
end;
TWPgtoDebitoAuto=class(TWPagamento)
{ ... }
_Conta : TWContaBancaria;
public
constructor Create (AConta: TWContaBancaria);
function efetuaPgto (ATitulo: TWTitulo) : integer;override;
end;
TWPgtoInternet=class(TWPagamento)
{ ... }
public
function efetuaPgto (ATitulo: TWTitulo) : integer;override;
end;
implementation
{ ... }
constructor TWPgtoDebitoAuto.Create (AConta: TWContaBancaria);
begin
_Conta := AConta;
{ ... }
end;
function TWPgtoDebitoAuto.efetuaPgto (ATitulo: TWTitulo) : integer;
begin
_Conta.Conecta;
_Conta.DebitaValor(ATitulo.ObtemValor);
ATitulo.RegistraPagto;
{ ... }
end;
A implementação da TWPgtoDebitoAuto no quadro acima mostra que podem ser necessárias outras informações para que a tarefa seja executada num determinado algorítmo. A função que executa a tarefa, no entanto, foi fixada na classe base (a interface) e não deve ser alterada já que isso implicaria que todas as outras heranças teriam que respeitar os novos parâmetros, o que nem sempre é desejável. Então, uma solução é passar as informações extras no construtor da própria classe.TWPgtoBoleto=class(TWPagamento)
{ ... }
public
function efetuaPgto (ATitulo: TWTitulo) : integer;override;
end;
TWPgtoDebitoAuto=class(TWPagamento)
{ ... }
_Conta : TWContaBancaria;
public
constructor Create (AConta: TWContaBancaria);
function efetuaPgto (ATitulo: TWTitulo) : integer;override;
end;
TWPgtoInternet=class(TWPagamento)
{ ... }
public
function efetuaPgto (ATitulo: TWTitulo) : integer;override;
end;
implementation
{ ... }
constructor TWPgtoDebitoAuto.Create (AConta: TWContaBancaria);
begin
_Conta := AConta;
{ ... }
end;
function TWPgtoDebitoAuto.efetuaPgto (ATitulo: TWTitulo) : integer;
begin
_Conta.Conecta;
_Conta.DebitaValor(ATitulo.ObtemValor);
ATitulo.RegistraPagto;
{ ... }
end;
Esse detalhe tem que ser levado em conta pela Factory responsável pela criação de instâncias da classe de pagamento. De acordo com o conceito de Factory, devemos criar uma função que centraliza a instanciação de uma família de classes:
TWTipoPagto = (tpBoleto, tpDebitoAuto, tpInternet);
function CriaNovoPagto (ATipo: TWTipoPagto; AContaDebito : TWContaBancaria) : TWPagamento;
begin
Result := Nil;
case (ATipo) of
tpBoleto:
Result := TWPgtoBoleto.Create;
tpDebitoAuto:
Result := TWPgtoDebitoAuto.Create (AContaDebito);
tpInternet:
Result := TWPgtoInternet.Create;
end;
end;
O último aspecto que falta abordar é com relação às características da classe de contexto TWTitulo. Além de propriedades inerentes a títulos - como seu valor, data de vencimento, código de barras, identificação do beneficiário, etc. - essa classe terá que manter uma instância da classe base de estratégia (o pagamento) para poder trocar mensagens com ela.
function CriaNovoPagto (ATipo: TWTipoPagto; AContaDebito : TWContaBancaria) : TWPagamento;
begin
Result := Nil;
case (ATipo) of
tpBoleto:
Result := TWPgtoBoleto.Create;
tpDebitoAuto:
Result := TWPgtoDebitoAuto.Create (AContaDebito);
tpInternet:
Result := TWPgtoInternet.Create;
end;
end;
TWTitulo=class
protected
_Id : String;
_DataVencto : TDateTime;
_Valor : Double;
_Pagto : TWPagamento;
_FoiPago : Boolean;
procedure RegistraPagto;
{ ... }
public
function ObtemValor : Double;
function Pagar (AMeioPgto: TWTipoPagto): integer;
{ ... }
end;
{ ... }
function TWTitulo.Pagar (AMeioPgto: TWTipoPagto): integer;
begin
if (_Pagto = Nil) then
_Pagto := CriaNovoPagto (AMeioPgto, _Sessao.ObtemContaBancaria);
_Pagto.efetuaPgto (Self);
end;
Com isso, temos a classe de contexto armazenando internamente uma instância do pagamento (a estratégia) e controlando seu ciclo de vida. A instância fica disponível para que o contexto possa utilizá-la sempre que for necessário executar a tarefa embutida nela.protected
_Id : String;
_DataVencto : TDateTime;
_Valor : Double;
_Pagto : TWPagamento;
_FoiPago : Boolean;
procedure RegistraPagto;
{ ... }
public
function ObtemValor : Double;
function Pagar (AMeioPgto: TWTipoPagto): integer;
{ ... }
end;
{ ... }
function TWTitulo.Pagar (AMeioPgto: TWTipoPagto): integer;
begin
if (_Pagto = Nil) then
_Pagto := CriaNovoPagto (AMeioPgto, _Sessao.ObtemContaBancaria);
_Pagto.efetuaPgto (Self);
end;
Note que a solução fica aberta para receber facilmente novos algoritmos, preservando o fraco acoplamento entre as classes já previstas e implementadas. Assim, poucos pontos têm que ser alterados para se introduzir uma nova forma de realizar a tarefa: basta codificar a nova classe e prever a criação de suas instâncias na função factory.
5 comentários :
Bom Dia. Estou tentando usar este conceito em um projeto meu. Surgiu uma dúvida. Criei a interface de pagamento. Nela preciso da referência do Título pra passar ele nos parâmetros. No objeto Título tenho que ter a referência da interface. Isso está causando um problema de referência circular pois estão em units diferentes. Como posso resolver isso?
Agora que descobri que na interface não precisa fazer o uses do Titulo.
TWTitulo = class;
TWPagamento=class
public
function efetuaPgto (ATitulo: TWTitulo) : integer;virtual;abstract;
end;
Bom Dia. Ainda não consegui sair do problema em questão. Me diz uma coisa por favor, a classe Context tem que ser declarada dentro da abstrata(interface) ? Pois se faço da forma acima não consigo acessar os métodos e atributos do TWTitulo pois ele fica de um tipo diferente de como ele realmente é, pois no meu caso fiz ele em outra unit como objeto Título mesmo. Como posso resolver este problema de referência circular? Se puder me ajudar ficarei muito grato.
Na forma como é sugerido no post, as classes estão numa mesma unit. Na sua estruturação, remova da declaração do TWTitulo a instância de TWPagamento (variável _Pagto) e restrinja seu uso ao método de pagamento (função Pagar do título). Assim, você pode colocar o uses para a unit do TWPagamento na área implementation da unit do TWTitulo, eliminando a referência cíclica entre essas classes.
Ok, muito obrigado pelas dicas. Este seu blog é uma enorme contribuição para a comunidade e uma excelente fonte de conhecimento. Obrigado por compartilhar este conhecimento.
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.