11 de abril de 2012

Novo IPad complica a vida dos desenvolvedores HTML5

Se você já achava dura a vida de desenvolvedor HTML, com as muitas diferenças de implementação nos diversos navegadores e a infinidade de tamanhos de telas disponíveis em desktops, tablets e celulares, veja só essa matéria publicada na InfoWorld sobre os teste feitos com os recursos do novo tablet da Apple - o iPad 3.
O novo iPad da Apple, já um sucesso entre os consumidores com sua tela de alta resolução, está, no entanto, decepcionando alguns desenvolvedores HTML5. O sistema operacional do tablet - iOS 5.1 - complica o armazenamento de dados do HTML5, não oferece novos suportes ao HTML5 e a performance na navegação web é, na melhor das hipóteses, similar à do iPad 2.

É demais classificar isso de "retrocesso" e ninguém está dizendo que a Apple está recuando de seu apoio agressivo aos padrões na web, o que eventualmente deixará aplicações web com comportamento próximo ao das aplicações nativas. Mas, para alguns, as decisões da Apple comprometem e poderiam viver sem elas.

A Sencha, fornecedora de ferramentas para HTML5, postou na semana passada uma "Avaliação HTML5' para o novo iPad e o iOS 5.1, classificando os resultados como um "saco misto" para a Apple. A avaliação do fornecedor pesou dois critérios: completude –- quanto dos vários elementos do HTML5 estão presentes –- e correção –- se o suporte a esses elementos está bem implementado, diz Aditya Bansod, diretor senior e gerente de produto da empresa de software sediada em Redwood City, Califórnia. O post também inclui resultados de comparativos com outros produtos em relação à performance do tablet na navegação.

"Ainda é a melhor plataforma HTML5 no mercado", diz Bansod. "Mas esperávamos um avanço maior que este (no novo iPad). Ao invés disso, estamos pisando em terreno pantanoso e até mesmo retrocedemos um pouco. É um tanto desapontador por parte da Apple."

Complicando o armazenamento de dados Web
Uma mudança, introduzida primeiro em 2011 com uma distribuição beta do iOS 5.1, limita alguns aspectos do armazenamento de dados local do HTML5. Dados armazenados localmente usando o LocalStorage do HTML5 não são mais tratados como persistentes pelo sistema operacional. Isto introduz um problema para desenvolvedores que usam este recurso para armazenar dados locais ou o WebSQL como mecanismo de armazenamento. Como o sistema não enxerga mais esses dados como persistentes e sim como temporários, "o iOS pode removê-los a qualquer momento, sem aviso, até em cenários onde o sistema está com pouca memória disponível", anotou Bansod em seu post.

Os desenvolvedores web rapidamente captaram a mudança em janeiro, discutindo-a em vários foruns online, como o forum Phonegap no Google Groups.

O problema afeta um sub grupo de aplicações do iOS, às vezes chamadas de aplicações híbridas, que utilizam uma WebView embutida. "WebViews são a força das aplicações HTML5 inseridas em pacotes nativos, como os do PhoneGap ou do Sencha Touch," escreveu Bansod. "Elas embutem um navegador web em aplicações nativas, o que permite a distribuição de aplicações web através das App Stores nativas. WebViews estão presentes em todos os sistemas operacionais mobile modernos."

Até o iOS 5.1, aplicações WebView podiam armazenar dados localmente e persistí-los usando o armazenamento do HTML5. "Especificamente, se sua aplicação usava LocalStorage ou WebSQL, isso era considerado parte dos dados da aplicação," diz Bansod. Se uma nova versão da aplicação fosse lançada, os dados ainda estariam presentes.

Este não é mais o caso. "É possível que isso se dê porque a Apple não conseguiu fazer de forma confiável a sincronização de dados pelo iCloud quando esses dados não estão armazenados no sistema de armazenamento nativo do iOS." especula Bansod. Um desenvolvedor disse no forum do Phonegap que ele foi informado por alguém da Apple que a razão para a mudança era "para economizar espaço, pois aplicações carregando muito conteúdo numa UIWebView (como o Twitter) ocuparia espaço demais [na sincronização com o serviço iCloud da Apple]... Mas eles esqueceram completamente de nós, pobres desenvolvedores Phonegap, que contavam com o LocalStorage ou o WebSQL para armazenar os dados dos usuários."

"Para os desenvolvedores que contavam com o LocalStorage ou o WebSQL como mecanismo para armazenar dados de suas aplicações, interrompê-lo é um grande problema," disse Bansod em seu post. Não é impeditivo: "Há vários meios de contornar o problema, como usar o SQLPlugin do PhoneGap que usa o SQLite padrão, or escrever seu próprio JavaScript para acessar diretamente o CoreData do iOS." Para muitos, afirma ele, isso significa recodificar suas aplicações.

De fato, aplicações que não forem alteradas "esquecerão" os dados. Os usuários também podem perder dados já que aplicações nas quais eles costumavam armazenar dados relevantes, de repente deixaram de fazer esse armazenamento, por exemplo.

Ao menos alguns desenvolvedores têm a esperança de que esta mudança seja apenas um bug que a Apple corrigirá. Em 7 de Março, com o anúncio do novo iPad e o iOS 5.1, eles descobriram que a Apple os colocou num novo território. "Eles fizeram isto. A Apple lançou o programa com aquele bug. Eu já tenho usuários furiosos porque perderam seus trabalhos com minha aplicação. :-/," postou Sam no forum do Phonegap.

As formas de contornar o problema não são simples, como se vê ao acompanhar as discussões para um plugin do Phonegap, criado por Shazron Abdullah na Apache Software Foundation.

Sem avanços nos recursos do HTML5
A avaliação da Sencha também revelou a ausência de quaisquer novas funções HTML5 no iOS 5.1 e na novíssima versão mobile do navegador Safari da Apple. "Nenhum novo recurso apareceu entre as versões iOS 5.0 e iOS 5.1," escreveu. "O iOS ainda apresenta um dos melhores suportes ao HTML5 entre os navegadores mobile, mas esta última encarnação não aprofundou o suporte do Safari aos padrões."

O Safari 6 no Mac, por exemplo, suporta um recurso chamado Regiões de Cascading Style Sheets (CSS), um modo simples de criar e modificar o layout de revistas digitais. Mas está faltando na versão atual do Safari para dispositivos com iOS 5.1.

"Queríamos ver também se o WebGL [uma API JavaScript para desenhar gráficos em 3D sem a necessidade de um plugin], que atualmente é suportado somente no Apple iAds, está disponível no navegador," Bansod escreveu. "haz.io [um site que avalia se seu navegador suporta padrões web emergentes] reporta que o WebGL é suportado pela versão mobile do Safari, mas, quando usamos o repositório de exemplos Khronos para testar, foi impossível fazer com que algum dos exemplos funcionasse."

Performance web
Para avaliar a performance web do novo iPad, a Sencha executou um conjunto de testes comparativos específicos da web usando um novo iPad, um iPad 2 (ambos com iOS 5.1), um tablet Motorola Xoom com Android 3.0, e um RIM PlayBook com Tablet OS 1.0. A equipe de Bansod executou o teste SunSpider e o V8 Benchmark Suite para medir o poder de processamento de códigos JavaScript.

Como é sabido, o novo iPad usa uma versão do chip dual-core A5 da Apple, com um novo processador gráfico quadcore.

No geral, o novo iPad (apelidado "Retina iPad" nos resultados dos testes da Sencha) foi um pouco mais lento em 6 dos 9 testes SunSpider. Nos sete testes do V8, o novo iPad empatou com o iPad 2 mas ambos perderam do tablet da Motorola.

Segundo Bansod, em muito da navegação pela internet, os usuários do novo iPad não verão problemas. Mas a diferença entre os dois iPads é perceptível quando se trata de desenhar páginas complexas. Por exemplo, o novo iPad estava visivelmente carregando novos blocos na parte de baixo de uma página de exemplo enquanto a página era rolada, coisa que raramente ocorria com o iPad 2.

Ele especula que uma razão para o patamar de desempenho do novo iPad é que a Apple acrescentou o processador gráfico quad-core e mais memória, mas não tornaram a memória mais rápida. Isso significaria, diz ele, que jogar imagens e outros recursos gráficos para a GPU está consumindo mais tempo e banda do que o dispositivo pode manipular em tempo real.

A matéria original em inglês pode ser acessada neste link.

3 de abril de 2012

Preparando aplicações Delphi para requerer incremento no Nível de Execução

Desde o Windows XP, a Microsoft vem implementando medidas que dificultam o acesso não autorizado a certos recursos do sistema, como o Registry, por exemplo. O intuito é incrementar a segurança do sistema operacional, evitando seu comprometimento ou até mesmo o roubo de informações. Esse esforço foi mais notado no Windows Vista, quando foi introduzido o UAC (User Account Control) para solicitar ao usuário permissão para acessar os recursos.

Com essa mudança, programas que antes liam tranquilamente o registro do Windows deixaram de funcionar. Dependendo de como o programa foi implementado, a exceção levantada pela falta de privilégio de acesso ao recurso pode até mesmo derrubar a aplicação com mensagens de erro pouco amistosas. O quadro abaixo traz um exemplo de código em Delphi que executa sem problemas no XP mas que não funcionará no Vista e no Win7 - a menos que o usuário comande a execução como Administrador:
procedure TForm1.BitBtn1Click(Sender: TObject);
var lReg: TRegistry;
begin
lReg := TRegistry.Create();

try
lReg.RootKey := HKEY_LOCAL_MACHINE;
lReg.OpenKey('Software\empresa', true);
lReg.WriteString('TipoImpressora', '0');
Application.MessageBox('Opção gravada com sucesso', 'Aviso', MB_OK);
except
on Erro: Exception do
Application.ShowException(Erro);
end;

lReg.Free;
end;
Felizmente, há um meio de informar ao sistema operacional que uma operação dessa natureza vai ocorrer e que, portanto, o usuário necessitará obrigatoriamente de privilégios de administrador para executar o programa. Esse processo é chamado de Requisição de Elevação do Nível de Execução e é feito através de um arquivo de manifesto que deve ser linkado junto com a aplicação.

O manifesto é um arquivo XML com estrutura bem definida, como a mostrada no quadro abaixo:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
name="ABC71.TesteManifest"
processorArchitecture="x86"
version="1.0.0.0"
type="win32"/>
<v3:trustInfo xmlns:v3="urn:schemas-microsoft-com:asm.v3">
<v3:security>
<v3:requestedPrivileges>
<v3:requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</v3:requestedPrivileges>
</v3:security>
</v3:trustInfo>
<description>TesteManifest</description>
</assembly>
O manifesto é composto de duas partes obrigatórias. O assembly é o nó raiz do XML, usado para aninhar as demais tags com as configurações em si. A tag interna assemblyIdentity identifica nossa aplicação, armazenando um nome único para ela e informando sua versão e a arquitetura para a qual ela foi desenhada. No exemplo, vemos que a nossa aplicação é Win32 e roda em processadores x86.

As outras tags são opcionais mas o nosso exemplo inclui também a tag v3:trustInfo, que é onde registramos a requisição do incremento de nível de execução. Tal requisição é feita no parâmetro level da tag requestedExecutionLevel, seguindo a estrutura mostrada acima. Quando informamos o valor requireAdministrator em level estamos dizendo ao Windows que o programa só pode executar se tiver privilégios de administrador. Então, antes de executá-lo, o Windows apresentará a tela para que o usuário forneça as credenciais do administrador - nome e senha - e só prosseguirá se elas estiverem corretas.

Para que essa configuração tenha efeito, precisamos criar um arquivo de recursos que aponta o manifesto e então, linká-lo ao programa Delphi. Um arquivo de recursos é um repositório onde são indicadas informações a serem agregadas a um programa, permitindo adicionar desde ícones e cursores até blocos de texto e dados binários para uso da aplicação. O arquivo de recurso pode ter extensão RC (quando é somente um texto) ou RES (resultado da compilação do RC). Supondo que o arquivo de manifesto se chame manifesto.manifest, um arquivo RC pode ser montado assim:
#define MANIFEST_RESOURCE_ID 1
MANIFEST_RESOURCE_ID 24 manifesto.manifest
O número 24 nesse arquivo indica ao Windows que o recurso em questão é um arquivo de manifesto. O arquivo RC deve ser adicionado ao projeto do Delphi e o arquivo de manifesto deve estar disponível na mesma pasta. Lembre-se que o Delphi cria automaticamente um arquivo RES com o mesmo nome do projeto; então, escolha um nome diferente do projeto para o RC extra.

Essa requisição de elevação de nível é especialmente útil em programas para configuração ou instalação de sistemas, situações que geralmente exigem privilégios mais altos para acesso a recursos protegidos, mais sensíveis. Na verdade, arquivos de manifesto são mais complexos do que o que foi apresentado aqui, servindo também para indicar se uma aplicação utilizará temas do Windows, se essa aplicação tem dependências de bibliotecas externas, entre outras coisas. O MSDN documenta o conteúdo permitido para esses arquivos neste link.

Versões mais recentes do Delphi permitem utilizar temas na criação de aplicações e, por isso, elas embutem automaticamente um manifesto em cada projeto. Assim, para conseguir inserir a requisição de incremento do nível de execução nestas versões, é preciso modificar a configuração do projeto, pedindo que se considere um arquivo de manifesto externo ao invés daquele que vem por padrão. Na página de configuração da aplicação - a mesma onde se muda o ícone do seu programa, selecione a opção "Use Custom Manifest" na caixa Runtime Themes. Depois, informe o caminho do seu arquivo de manifesto na caixa Custom Manifest, logo abaixo.

23 de março de 2012

Trabalhando com rotinas T-SQL no SQL Server

O SQL (Structured Query Language) é muito mas que apenas uma linguagem para manutenções num banco de dados. Ela não se restringe às quatro operações básicas (INSERT, DELETE, UPDATE e SELECT) ou àquelas funções para gerenciar tabelas ou transações. O SQL Server da Microsoft, por exemplo, implementa uma extensão do padrão SQL chamada T-SQL (Transact SQL) que permite construir funções complexas. Elas combinam queries com elementos típicos das linguagens de programação comuns, fornecendo uma sintaxe que inclui variáveis, loops e controle de fluxo.

Com essa linguagem, você pode construir Triggers, Stored Procedures, Functions ou simplesmente executar comandos pelo SQL Server Management Studio (função antigamente disponível no Query Analyser). Neste post, vou usar essa ferramenta para mostrar alguns dos recursos do T-SQL.

Para montarmos o exemplo básico, considere que vamos gerar uma lista de preços baseada nas informações contidas numa tabela chamada LISTAPR, criada de acordo com o comando a seguir:
-- Em SQL Server
create table LISTAPR
(
ID int not null,
PRODUTO varchar(25) null,
DESCRICAO varchar(255) null,
PRECO numeric(15,4) null,
DATA_VLD smalldatetime null
)

Como toda linguagem, o T-SQL possui comandos de declaração de variáveis para armazenar valores ao longo do programa. Basicamente, os tipos de dados permitidos são os mesmos que são válidos para criação de campos num tabela, com exceção dos tipos que representam BLOBs (text, ntext e image). A declaração em si é feita com a palavra chave DECLARE seguida de um arroba (@) acompanhando o nome da variável e, para finalizar, o tipo de dado que essa variável será capaz de armazenar. Exemplos:
DECLARE @produto varchar(25)
DECLARE @descricao varchar(255)
DECLARE @preco numeric(15,4)
DECLARE @linha varchar(300)
DECLARE @nro_regs int

SELECT @nro_regs = 0

A forma como os valores são recuperados do banco de dados nos obriga a declarar variáveis para os campos que pretendemos manipular. Assim, no trecho acima eu declarei uma variável para cada campo que será impresso. Como se vê também, o comando SELECT pode ser usado para atribuir manualmente valores para as variáveis.

Há um tipo especial de variável para ser usado na obtenção de um result set (lista de registros). O nome desse tipo é CURSOR e é através dele que estabelecemos quais dados serão recuperados do banco, qual o tipo de navegação será permitida nos registros (apenas avançar ou ir pra frente e pra trás) e também se será possível atualizar os dados. Para o cenário proposto neste post, precisaremos de uma coleção read-only que contenha todos os registros da tabela LISTAPR que estejam dentro da validade. A declaração abaixo atende esses requisitos:
DECLARE lista CURSOR SCROLL FOR
SELECT produto, descricao, preco
FROM LISTAPR
WHERE DATA_VLD > GETDATE() OR DATA_VLD IS NULL

O tipo da navegação é irrelevante aqui mas eu inclui a palavra SCROLL na declaração, possibilitando a navegação pelos registros em qualquer direção que queiramos. Outro detalhe é que eu estipulei quais campos eu quero recuperar. Isso é uma boa prática pois, conforme veremos adiante, deixa claro quais campos corresponderão a quais variáveis.

Agora, podemos abrir o cursor criado, navegar pela lista de registros obtidos, processar os dados conforme necessário e, finalmente, encerrar o cursor. O quadro abaixo traz o código com os passos citados:
OPEN lista
-- Primeiro registro
FETCH FIRST FROM lista INTO @produto, @descricao, @preco

WHILE @@FETCH_STATUS = 0
BEGIN
-- O processamento do registro atual será feito aqui

-- Próximo registro
FETCH NEXT FROM lista INTO @produto, @descricao, @preco
END

CLOSE lista
DEALLOCATE lista

Vamos destrinchar esse código. O comando OPEN apenas abre o cursor, submetendo a query ao banco de dados. O comando FETCH FIRST posiciona no primeiro registro e transfere para as variáveis os valores obtidos. Veja que a ordem das variáveis na cláusula INTO segue a mesma ordem estabelecida na cláusula SELECT.

A instrução WHILE é um laço tradicional, repetindo os comandos entre o BEGIN e o END enquanto o valor da expressão após o WHILE for verdadeira. Neste caso, @@FETCH_STATUS é uma variável nativa do T-SQL. Ela é afetada pela execução de comandos FETCH, assumindo valor 0 (zero) quando há um registro válido posicionado. Com isso, o comando FETCH NEXT, é executado enquanto ainda houver registros a recuperar no cursor.

Para encerrar, o cursor é fechado com o comando CLOSE e os recursos usados por ele são devolvidos ao sistema através do comando DEALLOCATE. Variáveis do tipo CURSOR são as únicas que requerem a desalocação explícita.

Ficou faltando a parte do processamento interno do laço. Como planejamos simplesmente imprimir os valores das variáveis, podemos usar o comando PRINT para isso. Ele aceita um texto como parâmetro, de modo que teremos que fazer a conversão dos tipos numéricos antes de imprimir.
SELECT @nro_regs = @nro_regs + 1, @linha = ''

SELECT @linha = @linha + @produto + REPLICATE (' ', 21 - LEN (@produto))
SELECT @linha = @linha + @descricao + REPLICATE (' ', 21 - LEN (@descricao))
SELECT @linha = @linha + STR (@preco, 10, 2)

PRINT @linha

Cada chamada ao PRINT gera uma nova linha na janela de saída do Management Studio, razão pela qual concateno os valores num único texto antes de imprimí-lo. Assim, cada produto terá sua própria linha. A ginástica com o REPLICATE e o LEN é para garantir que produto e descrição apareçam sempre com a mesma quantidade de caracteres, mantendo o resultado alinhado, mais fácil de ler. O STR converte o preço em um texto para a impressão.

É claro que, dada a simplicidade do exemplo, poderíamos ter resolvido o problema apenas executando a query. Mas o objetivo aqui era mostrar a flexibilidade da linguagem já que ela permite conter cursores aninhados, trabalhar com decisões de fluxo (instrução IF), cálculos complexos e outras interações. Aqui na ABC71, nós aproveitamos essa flexibilidade do T-SQL mesclada a queries que obtêm informações sobre tabelas do banco de dados para gerar certos tipos de codigo fonte auxiliares de nosso ERP.

13 de março de 2012

Design Patterns com Delphi: Visitor - Parte II

No último post, eu apresentei o conceito do Design Pattern Visitor usando para isso um diagrama UML com um exemplo prático da aplicabilidade do padrão. O exemplo consiste na representação de um Produto Acabado composto por uma lista de recursos (as matérias primas, máquinas e instruções) usados para fabricá-lo. O objetivo é permitir aplicar ao produto operações externas cujo resultado depende da aplicação da operação em cada parte que compõe a estrutura. No exemplo, há duas operações: uma que exporta a arquitetura do produto em formato XML e outra que faz a impressão dessa estrutura.

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:
Diagrama UML para o padrão Visitor
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.

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:
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.

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:
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.

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.