Neste post, mostro uma sugestão de implementação em Delphi para o exemplo do Visitor. Para facilitar a referência, publico novamente o diagrama que o retrata:
O primeiro passo é definir as classes que representam o propósito do sistema, estabelecendo as regras de negócio que constituirão o cerne dele. No nosso caso, essas classes são o recurso de produção, suas heranças e o produto acabado. O quadro abaixo mostra as declarações delas, que são bastante simples :
{ Recursos para a produção }
TWRecursoProducao=class
public
_Nome: String;
Constructor Create (ANome: String);virtual;
procedure Accept (AOper: TWOperacoesPA);virtual;
end;
TWMateriaPrima=class(TWRecursoProducao)
public
_CProd: String;
_Qtde: Double;
Constructor Create (ANome: String);override;
procedure Accept (AOper: TWOperacoesPA);override;
end;
TWMaquina=class(TWRecursoProducao)
public
_Cod: String;
_Tempo: Double;
_TempoSetup: Double;
Constructor Create (ANome: String);override;
procedure Accept (AOper: TWOperacoesPA);override;
end;
TWRoteiro=class(TWRecursoProducao)
public
_Cod: String;
_Texto: String;
Constructor Create (ANome: String);override;
procedure Accept (AOper: TWOperacoesPA);override;
end;
{ Produto Acabado }
TWProdutoAcabado=class
protected
_MatPrimas : TObjectList;
_Maquinas : TObjectList;
_Roteiro : TWRoteiro;
procedure ClearListaRec (ALista: TObjectList);
public
_Nome: String;
Constructor Create (ANome: String);
Destructor Destroy; override;
procedure ClearRecursos;
procedure AddMatPrima (ARecurso: TWRecursoProducao);
procedure AddMaquina (ARecurso: TWRecursoProducao);
procedure SetRoteiro (ARoteiro: TWRoteiro);
procedure DoOperacao (AOper: TWOperacoesPA);
end;
Veja que a classe que representa o produto acabado (TWProdutoAcabado) possui membros do tipo TObjectList e também um roteiro separado. Esses membros armazenarão os recursos necessários para fabricar o produto acabado. Neste exemplo, as classes que compõem a agregação são heranças de uma mesma classe base - a TWRecursoProducao - mas o padrão Visitor não faz essa exigência. Assim, ele ainda é aplicável mesmo quando a agregação é composta por classes que não guardam relação entre si. Então, usei variáveis separadas pra cada tipo de recurso apenas para deixar clara essa possibilidade.TWRecursoProducao=class
public
_Nome: String;
Constructor Create (ANome: String);virtual;
procedure Accept (AOper: TWOperacoesPA);virtual;
end;
TWMateriaPrima=class(TWRecursoProducao)
public
_CProd: String;
_Qtde: Double;
Constructor Create (ANome: String);override;
procedure Accept (AOper: TWOperacoesPA);override;
end;
TWMaquina=class(TWRecursoProducao)
public
_Cod: String;
_Tempo: Double;
_TempoSetup: Double;
Constructor Create (ANome: String);override;
procedure Accept (AOper: TWOperacoesPA);override;
end;
TWRoteiro=class(TWRecursoProducao)
public
_Cod: String;
_Texto: String;
Constructor Create (ANome: String);override;
procedure Accept (AOper: TWOperacoesPA);override;
end;
{ Produto Acabado }
TWProdutoAcabado=class
protected
_MatPrimas : TObjectList;
_Maquinas : TObjectList;
_Roteiro : TWRoteiro;
procedure ClearListaRec (ALista: TObjectList);
public
_Nome: String;
Constructor Create (ANome: String);
Destructor Destroy; override;
procedure ClearRecursos;
procedure AddMatPrima (ARecurso: TWRecursoProducao);
procedure AddMaquina (ARecurso: TWRecursoProducao);
procedure SetRoteiro (ARoteiro: TWRoteiro);
procedure DoOperacao (AOper: TWOperacoesPA);
end;
Repare ainda na função Accept introduzida na classe base de recursos. É ela quem define a família de classes como Visitable, determinando que os recursos de produção que elas representam poderão ser visitados por qualquer operação externa herdada de TWOperacoesPA. Na verdade, Accept simplesmente solicitará à operação que "visite" o recurso em questão, o que fará com que a operação seja aplicada ao recurso :
procedure TWMateriaPrima.Accept (AOper: TWOperacoesPA);
begin;
inherited;
AOper.Visit (Self);
end;
procedure TWMaquina.Accept (AOper: TWOperacoesPA);
begin;
inherited;
AOper.Visit (Self);
end;
procedure TWRoteiro.Accept (AOper: TWOperacoesPA);
begin;
inherited;
AOper.Visit (Self);
end;
A seguir, podemos definir a hierarquia de classes que implementarão as operações externas nos recursos de produção. Tais classes exercerão o papel de Visitors:
begin;
inherited;
AOper.Visit (Self);
end;
procedure TWMaquina.Accept (AOper: TWOperacoesPA);
begin;
inherited;
AOper.Visit (Self);
end;
procedure TWRoteiro.Accept (AOper: TWOperacoesPA);
begin;
inherited;
AOper.Visit (Self);
end;
TWOperacoesPA=class
public
procedure InitOper(AInfo: TObject);virtual;
procedure TermOper;virtual;
procedure Visit (pRecurso: TWMateriaPrima);overload;virtual;
procedure Visit (pRecurso: TWMaquina);overload;virtual;
procedure Visit (pRecurso: TWRoteiro);overload;virtual;
end;
TWRecursoSaveToXml=class(TWOperacoesPA)
protected
_Xml : String;
public
procedure InitOper(AInfo: TObject);override;
procedure TermOper;override;
procedure Visit (pRecurso: TWMateriaPrima);overload;override;
procedure Visit (pRecurso: TWMaquina);overload;override;
procedure Visit (pRecurso: TWRoteiro);overload;override;
function GetXML : String;
end;
A operação para imprimir o produto acabado é bastante similar à de salva para XML e, por isso, eu a omiti do quadro.public
procedure InitOper(AInfo: TObject);virtual;
procedure TermOper;virtual;
procedure Visit (pRecurso: TWMateriaPrima);overload;virtual;
procedure Visit (pRecurso: TWMaquina);overload;virtual;
procedure Visit (pRecurso: TWRoteiro);overload;virtual;
end;
TWRecursoSaveToXml=class(TWOperacoesPA)
protected
_Xml : String;
public
procedure InitOper(AInfo: TObject);override;
procedure TermOper;override;
procedure Visit (pRecurso: TWMateriaPrima);overload;override;
procedure Visit (pRecurso: TWMaquina);overload;override;
procedure Visit (pRecurso: TWRoteiro);overload;override;
function GetXML : String;
end;
Veja que a classe TWOperacoesPA, que é base para todas as operações, possui 3 métodos Visit sobrecarregados (overload). Cada um deles trata um tipo diferente de recurso, o que, na prática, nos permite adequar o comportamento da operação. Isso quer dizer que a operação será realizada de um modo coerente com o tipo do recurso.
Um outro detalhe nessa classe é a função InitOper. Ela é desenhada para realizar procedimentos iniciais da operação, aceitando um parâmetro genérico do tipo TObject com informações que façam sentido para a operação. Por exemplo, pode representar uma impressora para a operação de Imprimir ou um objeto DOM para a exportação em formato XML.
O trecho de código abaixo mostra as funções de inicialização e encerramento da operação de exportação para XML. Também retrata a versão da função Visit sobrecarregada para os tipos de recurso "Matéria Prima" e "Máquina":
procedure TWRecursoSaveToXml.InitOper(AInfo: TObject);
var lProd: TWProdutoAcabado;
begin
inherited;
{Abre a tag raiz para o XML }
lProd := AInfo As TWProdutoAcabado;
_XML := '<?xml version="1.0" encoding="ISO-8859-1"?>' + #13#10;
_XML := _XML + '<produtoAcabado name="' + lProd._Nome + '">' + #13#10;
end;
procedure TWRecursoSaveToXml.TermOper;
begin
{ Fecha a tag XML raiz }
_XML := _XML + '</produtoAcabado>';
inherited;
end;
procedure TWRecursoSaveToXml.Visit (pRecurso: TWMateriaPrima);
begin
inherited;
{ Versão específica dessa operação p/ o recurso "Matéria Prima" }
_XML := _XML + ' <materiaPrima>' + #13#10;
_XML := _XML + ' <codigo>' + pRecurso._CProd + '</codigo>' + #13#10;
_XML := _XML + ' <nome>' + pRecurso._Nome + '</nome>' + #13#10;
_XML := _XML + ' <qtde>' + FormatFloat ('#,##0.00', pRecurso._Qtde) + '</qtde>' + #13#10;
_XML := _XML + ' </materiaPrima>' + #13#10;
end;
procedure TWRecursoSaveToXml.Visit (pRecurso: TWMaquina);
begin
inherited;
{ Versão específica dessa operação p/ o recurso "Máquina" }
_XML := _XML + ' <maquina>' + #13#10;
_XML := _XML + ' <codigo>' + pRecurso._Cod + '</codigo>' + #13#10;
_XML := _XML + ' <nome>' + pRecurso._Nome + '</nome>' + #13#10;
_XML := _XML + ' <setup>' + FormatFloat ('#,##0.00', pRecurso._TempoSetup) + '</setup>' + #13#10;
_XML := _XML + ' <tempo>' + FormatFloat ('#,##0.00', pRecurso._Tempo) + '</tempo>' + #13#10;
_XML := _XML + ' </maquina>' + #13#10;
end;
Por fim, podemos implementar a função que aplica a operação ao produto acabado. Por definição, isso implica aplicar a mesma operação sobre os recursos que compõe esse produto acabado. Por isso, teremos que passar por todos os recursos, chamando a função Accept para determinar como a operação deve ser executada em cada um deles:
var lProd: TWProdutoAcabado;
begin
inherited;
{Abre a tag raiz para o XML }
lProd := AInfo As TWProdutoAcabado;
_XML := '<?xml version="1.0" encoding="ISO-8859-1"?>' + #13#10;
_XML := _XML + '<produtoAcabado name="' + lProd._Nome + '">' + #13#10;
end;
procedure TWRecursoSaveToXml.TermOper;
begin
{ Fecha a tag XML raiz }
_XML := _XML + '</produtoAcabado>';
inherited;
end;
procedure TWRecursoSaveToXml.Visit (pRecurso: TWMateriaPrima);
begin
inherited;
{ Versão específica dessa operação p/ o recurso "Matéria Prima" }
_XML := _XML + ' <materiaPrima>' + #13#10;
_XML := _XML + ' <codigo>' + pRecurso._CProd + '</codigo>' + #13#10;
_XML := _XML + ' <nome>' + pRecurso._Nome + '</nome>' + #13#10;
_XML := _XML + ' <qtde>' + FormatFloat ('#,##0.00', pRecurso._Qtde) + '</qtde>' + #13#10;
_XML := _XML + ' </materiaPrima>' + #13#10;
end;
procedure TWRecursoSaveToXml.Visit (pRecurso: TWMaquina);
begin
inherited;
{ Versão específica dessa operação p/ o recurso "Máquina" }
_XML := _XML + ' <maquina>' + #13#10;
_XML := _XML + ' <codigo>' + pRecurso._Cod + '</codigo>' + #13#10;
_XML := _XML + ' <nome>' + pRecurso._Nome + '</nome>' + #13#10;
_XML := _XML + ' <setup>' + FormatFloat ('#,##0.00', pRecurso._TempoSetup) + '</setup>' + #13#10;
_XML := _XML + ' <tempo>' + FormatFloat ('#,##0.00', pRecurso._Tempo) + '</tempo>' + #13#10;
_XML := _XML + ' </maquina>' + #13#10;
end;
procedure TWProdutoAcabado.DoOperacao (AOper: TWOperacoesPA);
var i : integer;
lMatPrima: TWMateriaPrima;
lMaquina : TWMaquina;
begin
AOper.InitOper (Self);
{ Aplica a operação a cada matéria prima da lista }
for i := 0 to _MatPrimas.Count - 1 do
begin
lMatPrima := _MatPrimas.Items[i] As TWMateriaPrima;
lMatPrima.Accept (AOper);
end;
{ Aplica a operação a cada máquina da lista }
for i := 0 to _Maquinas.Count - 1 do
begin
lMaquina := _Maquinas.Items[i] As TWMaquina;
lMaquina.Accept (AOper);
end;
{ Aplica a operação no roteiro, se houver um }
if (_Roteiro <> Nil) then
_Roteiro.Accept (AOper);
AOper.TermOper;
end;
{ ... }
var lOperacao : TWRecursoSaveToXml;
lProd : TWProdutoAcabado;
lXml : String;
begin
lProd := TWProdutoAcabado.Create ('Computador');
lOperacao := TWRecursoSaveToXml.Create;
lProd.DoOperacao(lOperacao);
{ Publica o resultado da operação }
lXml := lOperacao.GetXML ();
{ ... }
end;
A parte inferior do quadro demonstra como se dá a aplicação de uma operação.var i : integer;
lMatPrima: TWMateriaPrima;
lMaquina : TWMaquina;
begin
AOper.InitOper (Self);
{ Aplica a operação a cada matéria prima da lista }
for i := 0 to _MatPrimas.Count - 1 do
begin
lMatPrima := _MatPrimas.Items[i] As TWMateriaPrima;
lMatPrima.Accept (AOper);
end;
{ Aplica a operação a cada máquina da lista }
for i := 0 to _Maquinas.Count - 1 do
begin
lMaquina := _Maquinas.Items[i] As TWMaquina;
lMaquina.Accept (AOper);
end;
{ Aplica a operação no roteiro, se houver um }
if (_Roteiro <> Nil) then
_Roteiro.Accept (AOper);
AOper.TermOper;
end;
{ ... }
var lOperacao : TWRecursoSaveToXml;
lProd : TWProdutoAcabado;
lXml : String;
begin
lProd := TWProdutoAcabado.Create ('Computador');
lOperacao := TWRecursoSaveToXml.Create;
lProd.DoOperacao(lOperacao);
{ Publica o resultado da operação }
lXml := lOperacao.GetXML ();
{ ... }
end;
O projeto com esse exemplo pode ser salvo a partir desse link. Ele foi criado em Delphi 2005 mas deve ser possível compilá-lo em outras versões do IDE sem problemas.
Um comentário :
Muito boa sua série sobre Design Patterns estou aprendendo muito e só tenho a agradecer.
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.