28 de fevereiro de 2011

Traçando rotas com a versão 3 da API do Google Maps - Parte II

Traçar rotas usando a API do Google Maps é uma tarefa relativamente simples. Para o processo básico, basta criar instâncias de dois serviços existentes na API - um que calcula as rotas (DirectionsService) e outro que as exibe (DirectionsRenderer). Além, é claro, de fornecermos o endereço de origem e o de destino (ou pares latitude/longitude que correspondam aos locais desejados). Esse processo básico foi assunto de um outro post aqui no blog, onde foi apresentado um passo a passo para esse uso da API.

Rotas onde apenas dois pontos são especificados atendem uma gama grande de situações, mas nem sempre isso é suficiente. É o que ocorre quando há uma logística mais complexa envolvida no traçado da rota, como por exemplo, quando temos um ponto de origem e outro de destino mas queremos também estabelecer locais intermediários que devem constar da rota calculada.

Na realidade, o trabalho feito no post básico sobre rotas nos deixa bem próximos de atender também a necessidade de passar por outros pontos preestabelecidos. O que vai abaixo é parte do código explicado naquele outro post, justamente o trecho que traça a rota entre dois locais dados:
var directionsService, directionsRenderer;
var enderDe, enderAte;

directionsService = new google.maps.DirectionsService();
directionsRenderer = new google.maps.DirectionsRenderer();
directionsRenderer.setMap(map);

enderDe = 'ALAMEDA SANTOS, 1000, SÃO PAULO - SP, 01418-9028';
enderAte =
'AVENIDA NAÇÕES UNIDAS, 17-17, BAURU - SP, 17013-035';

var request = {
origin:endDe,
destination:endPara,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};

directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsRenderer.setDirections(response);
}
});)

A variável request nesse código é do tipo DirectionsRequest. De acordo com a documentação dessa classe, além das propriedades utilizadas no exemplo básico acima, ela possui uma outra chamada waypoints. A propriedade waypoints é um array de objetos do tipo DirectionsWaypoint, cada um contendo informações sobre um endereço intermediário que deve constar do trajeto final. Cada endereço pode ser informado como um texto - exatamente como fazemos para definir os pontos de origem e destino - ou ainda o par latidude/longitude que representa o local por onde a rota deve passar.

Com isso, podemos montar um novo exemplo para traçar a rota entre dois locais, passando obrigatoriamente por uma lista de outros locais, cujos endereços são fornecidos. É preciso apenas passar uma lista desses endereços usando a propriedade waypoints do objeto request:
var directionsService, directionsRenderer;
var enderDe, enderAte;

directionsService = new google.maps.DirectionsService();
directionsRenderer = new google.maps.DirectionsRenderer();
directionsRenderer.setMap(map);

/* Endereço de origem e o de destino. */
enderDe = 'ALAMEDA SANTOS, 1000, SÃO PAULO - SP, 01418-9028';
enderAte =
'AVENIDA NAÇÕES UNIDAS, 17-17, BAURU - SP, 17013-035';

/* Locais intermediários por onde a rota deve passar. */
var local1, local, local3;
local1 = {location:'AV. 11 DE JUNHO, 52, SÃO PAULO - SP'};
local2 = {location:'RUA CEL. MURSA, 10, SÃO PAULO - SP'};
local3 = {location:'RUA CAMPOS SALLES, 214, BOTUCATU - SP'};

var request = {
origin:endDe,
destination:endPara,
travelMode: google.maps.DirectionsTravelMode.DRIVING,
waypoints: new Array (local1, local2, local3)
};

Acrescentando esse valor do waypoints ao exemplo básico publicado no outro post, temos o mapa e a rota abaixo:

O HTML com este exemplo completo pode ser acessado neste link.

Veja que os endereços da lista não precisam estar na mesma cidade. Outro aspecto importante a se destacar é que a rota é traçada a partir do local de origem, passando pelos pontos extras na ordem exata em que eles foram incluídos na lista. Isto é, o serviço da API que traça as rotas não faz qualquer inferência a respeito da melhor ordem a seguir para obter a rota mais curta. Portanto, quando essa questão for um problema, é preciso prestar atenção ao construir o array waypoints para evitar que a rota resultante seja irracional.

Por fim, como a propriedade waypoints é do tipo Array, todos os métodos e propriedades dessa classe são aplicáveis, inclusive push, que permite acrescentar novos itens ao array depois que ele foi instanciado:
request.waypoints.push (
{location: 'AV. RODRIGUES ALVES, 14-10, BAURU - SP' }
);

A documentação para a classe Array está disponível neste link.

Mais Informações
Básico sobre rotas com a versão 3 da API do Google Maps, Documentação da classe DirectionsRequest, Documentação da classe DirectionsWaypoint

22 de fevereiro de 2011

Ferramenta da Adobe permite criar aplicações Flash para IPhone

Semana passada eu finalmente me animei a procurar o SDK do iOS para baixá-lo e fazer testes básicos de criação de aplicações para o iPhone e o iPad, só pra ver como é que funciona. Para minha surpresa, descobri pela documentação que a Apple só tem versão do SDK para Mac OS ! Simplesmente não há opção de desenvolvimento em Windows - apenas gambiarras ... Levando em conta que o Windows está presente em mais de 80% dos desktops, é no mínimo estranha a opção da Apple pela exclusividade da versão para Mac.

Por incrível que pareça, a solução para esse impasse pode ser desenvolver em Flash. Apesar da resistência da Apple em implementar na versão mobile de seu navegador Safari o recurso para executar animações Flash, há boas notícias neste campo. É que a Adobe lançou em outubro do ano passado uma release de suas ferramentas focando no desenvolvimento para dispositivos móveis e incluiu no pacote um programa capaz de compilar o código por trás das aplicações Flash - o ActionScript - diretamente como um programa nativo para iOS, sistema operacional de iPads, iPhones e iPods Touch.

O texto abaixo, que é uma tradução de matéria publicada no site Infoworld.com, lança algumas luzes sobre esse assunto. A matéria original em inglês pode ser acessada aqui.

Há algum tempo, o caminho para a Apple App Store era muito simples para desenvolvedores Adobe Flash: abandonar a facilidade dos métodos que eles estavam acostumados e se devotar à pura complexidade do Objective-C. As vistosas ferramentas e bibliotecas de renderização dessa linguagem são boas para os iniciantes mas apenas especialistas em ponteiros e malloc são bem-vindos ao banquete à mesa do iOS. A porta era fechada nos dedos dos demais.

A razão era simples: a Apple se recusava a aceitar programas que usassem bibliotecas ou interpretadores e, tal qual as antigas professoras, insistia que cada um escrevesse seu próprio código. Talvez a Apple temesse vírus, códigos baixados pela internet ou a competição de ferramentas multiplataformas.

Isso foi antes. Agora, a Apple cedeu um pouco e não está mais completamente fechada ao uso de plataformas runtime como o Flash para desenvolvimento de aplicações para iPhone e iPad. São boas novas para aqueles que se especializaram num conjunto de ferramentas que continua a produzir alguns dos conteúdos mais vistosos da Web.

"Basicamente, a renderização da Adobe está há anos sendo construída e eles aperfeiçoaram esta tecnologia", diz Paulius Uza, CEO da InRuntime, criadora do jogo Alchemist. A empresa dele frequentemente prototipa ideias com outras tecnologias, como o OpenGL, mas ele sustenta que "uma versão em Flash sempre parece mais bonita."

Agora, desenvolvedores Flash como Uza têm outros caminhos para o iPhone e o iPad, através da Adobe e de um outro bom competidor, criado por gente que já trabalhou para a Adobe. Ambos abrem oportunidades para que aqueles que já trabalham com o ecossistema Flash possam usar seus talentos e códigos para criar novas aplicações.

A ferramenta Packager for iPhone pega um código em ActionScript 3 e o compila para que execute em dispositivos com iOS 3.0 ou mais recente. O resultado é código nativo e não um código binário Flash interpretado; para a Apple, este passo de empacotamento garante que você não estará entregando novos binários ao dispositivo através da internet e sobrepujando os mecanismos que resguardam a Apple App Store.

O Packager for iPhone é melhor para desenvolvedores que dominam a criação de websites em Flash. Uza disse que seu grupo gosta dessa opção porque ela oferece mais controle para quem prefere pensar em linhas de código, como programadores. "Estamos escrevendo código com o Flash Builder, que é voltado a desenvolvedores somente. Ele não possui qualquer ferramenta relativa ao desenvolvimento do visual de aplicações", nota ele.

Criadores mais casuais, como designers gráficos, ficam mais confortáveis em uma aplicação integrada e podem usar o Flash Professional CS5, o qual agora possui o Packager for iPhone nativo. Ao invés de salvar o projeto para a web, você seleciona a opção que converte o projeto em uma aplicação pronta para ir para o App Store. Ainda é exigido que o desenvolvedor tenha um certificado da Apple, obtido quando você se cadastra no programa deles para desenvolvedores de aplicações. No entanto, a ferramenta da Adobe faz todo o resto.

O outro caminho é o Corona SDK, criado pela Ansca Mobile, companhia fundada por pessoas que trabalharam na equipe do Flash. Embora esse SDK use Lua ao invés do ActionScript como linguagem, a estrutura das aplicações e a API são muito similares ao Flash. O CEO Walter Luh diz "Muita gente achará que esse é um produto extremamente fácil de ser compreendido."

Ambas as ferramentas também prometem oportunidades multiplataformas. A Google deu boas vindas aos desenvolvedores do Flash para Android, assim como a Hewlett-Packard o fez em relação ao WebOS 2.1, que ainda será lançado. A Adobe também planeja assegurar que códigos Flash executem no BlackBerry Tablet OS, sistema do novo tablet da RIM, o PlayBook. O Corona suporta iOS e Android mas poderá acrescentar outras plataformas se surgir demanda.

Estas ferramentas são tentadoras para qualquer desenvolvedor Flash com uma pilha de código que já roda na web.

"Em alguns casos, você terá simplesmente que fazer o porte", diz Richard Galvan, gerente de produtos Flash na Adobe Systems. "Adapte o projeto para uma tela menor e então poderá liberá-lo muito rapidamente para o iOS. Você pode literalmente pegar o mesmo projeto e, num passo seguinte, publicar a aplicação também para Android."

Ele adverte, porém, que embora o Adobe packager faça tudo funcionar corretamente, podem ocorrer problemas com a interface gráfica da aplicação portada. Por exemplo, smartphones têm tela pequena e frequentemente vêm sem teclado, o que não costuma ser preocupação no desenvolvimente de aplicações desktop. Na mesma linha, os eventos touch se comportam ligeiramente diferente dos cliques com o mouse. Assim, muitas aplicações desktop em Flash vão requerer alguma reavaliação na forma como um usuário interage com elas.

Há também uma quantidade considerável de retrabalho a se fazer com os gráficos. Embora todas a parte gráfica seja exibida quando portada sem modificações, a escala das imagens é com frequência inadequada para dispositivos menores. Assim, o que se vê fácilmente na tela de um PC, geralmente fica muito pequeno num iPhone. O maior desafio aparece porque há muito menos espaço numa tela pequena.

Desafios maiores surgem em relação ao estilo de arte. Algumas apresentações em Flash se baseiam em arte vetorial, que permite exibir o mesmo visual em escalas diferentes. Embora isto funcione no iPhone, a renderização de gráficos vetoriais neste aparelho é notadamente menos veloz que um mapa de bits, pois este é processado pelo hardware gráfico do smartphone. Ajustando a propriedade cacheAsBitmap pode acelerar a renderização dos gráficos vetoriais - isto é especialmente importante se a imagem de fundo é vetorial porque todo a imagem é redesenhada, mesmo que apenas algum elemento do primeiro plano tenha sido modificado.

Tom Barclay, gerente de projeto na Adobe, diz que a empresa está procurando maior automatização no processo de conversão de forma que o packager instantaneamente trate a imagem vetorial corretamente, de acordo com o tamanho da tela a que se destina. Os desenvolvedores já sabem que tirar vantagem da resolução extra do novo visor Retina do iPhone requer um conjunto diferente de imagens, redesenhadas em resoluções mais altas. Idealmente, a ferramenta converteria as imagens vetoriais e as prepararia como mapas de bits, tirando vantagem de resoluções ainda mais altas no futuro.

O desafio de mudar para Corona da Ansca é um pouco diferente para programadores Flash porque a linguagem, Lua, não é idêntica ao ActionScript. As diferenças são cosméticas e há relatos de que é preciso apenas retrabalhar o código, junto com passos simples como substituir o "abre chave" por palavras como "do".

Luh, da Ansca, diz que portar o código é geralmente simples porque as estruturas de dados são relativamente similares e descomplicadas. Por exemplo, Lua oferece apenas um tipo de tabela - uma coleção de pares nome-valor não tipados.

Jonathan Beebe, um desenvolvedor que criou jogos como Cavern Drake com sua esposa e os publica sob o nome de Beebe Games, diz que foi atraído pelo Corona porque Lua é muito menos intimidadora que os ponteiros que dominam a sintaxe do C. "A primeira vez que cruzei com ela, eu estava prestes a aprender Objective-C," ele recorda. Ele notou que Lua é muito similar ao PHP, que ele já tinha usado antes. "É muito fácil a transição do PHP para Lua."

Fora a conversão de código ActionScript para Lua, desenvolvedores Corona têm o mesmo desafio de fazer caber uma aplicação desktop dentro da tela pequena dos smartphones - com frequência, um desafio maior do que portar a lógica da aplicação. "O engraçado é que por causa da alta definição do iPhone, desenvolvedores gastam mais tempo atualizando os gráficos e trazendo-os para a qualidade do iOS," nota Luh.

Uma das maiores diferenças entre desenvolver em Flash e em Corona são as bibliotecas. A plataforma Flash está bem estabelecida e há centenas de bibliotecas, tanto comerciais quanto open source, disponíveis aos programadores que queiram usá-las.

O Corona, por outro lado, vem com diversos recursos que desenvolvedores Flash usando as ferramentas da Adobe só obtêm através do uso de bibliotecas externas, o que torna as versões em Corona mais simples. Um exemplo do Corona é chamado "Física em 5 Linhas," um projeto simples que ilustra como objetos podem ser criados e postos em movimento apenas configurando umas poucas propriedades. Você pode usar essa técnica pra construir jogos básicos que empregam física simples na criação de um mundo, e então ver esses objetos se desintegram ao se chocarem. Luh disse que viu desenvolvedores clonarem o Angry Birds em cerca de um dia de trabalho.

Beebe disse que gostou de trabalhar como o engine físico porque ele simplificou muito do trabalho árduo envolvido na criação de jogos arcade que acuradamente simulem o comportamento de objetos bi-dimensionais na Terra. "É realmente bom," diz. Ele também nota que o engine é baseado na biblioteca open source Box2d que oferece um modo de física contínuo para simular como objetos reagem quando batem uns nos outros, afetando a direção em que eles se moviam.

O Corona SDK também oferece acesso a várias bibliotecas para mergulhar nas entranhas do smartphone, incluindo conexão com a câmera, alto falantes, GPS e o acelerômetro. Uma das mais surpreendentes é a biblioteca nativa para postar atualizações no Facebook.

Beebe estava particularmente agradecido pela mais nova biblioteca, que facilita a implementação de compras a partir da aplicação. "Eu tenho lido histórias tenebrosas sobre esta implementação em aplicações," diz. "No Corona, o mais complicado é lidar com o iTunes Connect, mas isso todos tem que fazer, independentemente do SDK usado."

Um dos maiores atrativos das ferramentas da Adobe e da Ansca é a oportunidade de escrever o mesmo código para múltiplas plataformas. Mas a realidade não é nem perto de ser tão atrativa quanto a promessa.

Mark Sigal, co-founder da Unicorn Labs, um desenvolvedor de aplicações móveis, diz que apesar dos desenvolvedores poderem simplesmente escolher a opção "Salvar Como" do menu, eles dificilmente ficam satisfeitos com o resultado. "É como todo o resto: haverá aqueles 10 ou 15 porcento do tempo que você gastará refinando a aplicação para seu dispositivo alvo," explica.

Sigal disse que a proliferação de dispositivos Android é um desafio especialmente oneroso porque há muitos tamanhos e versões diferentes com ligeiras diferenças de configuração de hardware. Mas usando uma plataforma de desenvolvimento neutra como Flash ou Corona mantém essa variabilidade gerenciável, ele diz: "Efetivamente estamos suportando 22 alvos diferentes." Graças à ferramenta, "não é um trabalho complicado."

Não cheguei a testar nenhuma dessas duas ferramentas mas qualquer uma delas pareceu melhor opção do que desenvolver usando diretamente o SDK do iOS. Primeiro, porque eu não teria que abandonar meu ambiente de desenvolvimento em Windows, onde, bem ou mal, eu me sinto em casa. Depois, como a própria matéria aponta, as ferramentas abrem a oportunidade de se construir aplicações para múltiplas platataformas praticamente com o mesmo código e sem muitos traumas.

16 de fevereiro de 2011

Design Patterns com Delphi: Memento – Parte II

Neste post, eu apresento uma proposta de implementação em Delphi para o Design Pattern comportamental Memento, introduzido no meu último post. Lá, o padrão foi conceituado com a ajuda de um exemplo, cujo diagrama UML eu reproduzo abaixo:
Diagrama UML para o padrão Memento

Embora não seja especialmente complexa, a implementação em Delphi se baseia num detalhe da linguagem que é poucas vezes citado, relacionado com as regras de visibilidade dos membros de uma classe. Como no C++, o Delphi possui basicamente 3 níveis de visibilidade: private (onde os membros só podem ser manipulados pela classe que os declarou); protected (onde os membros são diretamente acessíveis tanto pela classe que os declarou quanto por qualquer outra que seja uma herança da classe original); e public (onde os membros assim declarados são universalmente acessíveis, isto é, qualquer parte do programa tem acesso a eles).

Entretanto, em classes declaradas numa mesma unit há flexibilização nas regras de visibilidade, fazendo com que os membros protegidos de uma classe sejam acessíveis pelas outras, enquanto os membros privados passam a ser visíveis para as heranças. Ou seja, agem mais ou menos como as classes associadas com a palavra chave friend em C++. Você ainda pode forçar que uma unit siga as regras de visibilidade válidas nos demais contextos; para isso, acrescente a palavra reservada strict ao nome da regra tradicional. Assim, temos na prática duas novas regras, strict private e strict protected.

Isto posto, a solução para o Memento em Delphi fica bastante simples. O relacionamento entre as classes TWCliente e TWClienteStatus exige que ao menos uma delas enxergue propriedades não-públicas da outra, pois o estado da TWCliente deverá ser armazenado internamente em TWClienteStatus. Nenhuma dessas informações, no entanto, poderá estar disponível fora desse contexto, sob pena de violar o encapsulamento. Então, as classes citadas devem ser declaradas na mesma unit para garantir o tipo de acesso necessário:
TWClienteStatus = class;

TWCliente = class
strict private
_CreditoEmUso: double;
_LimiteCredito: double;

public
Constructor Create;

function RealizaOpCredito (Valor: Double) : boolean;
function RealizaPagto (Valor: Double) : boolean;
function SalvaStatus : TWClienteStatus;
procedure RestauraStatus (Estado: TWClienteStatus);
end;

TWClienteStatus = class
protected
_Dados: TMemoryStream;

procedure AdicInfoDbl (Valor: Double);
procedure AdicInfoStr (Valor: String);

procedure InitGetInfo;
function ExtraiInfoDbl : Double;
function ExtraiInfoStr : String;

public
Constructor Create;
Destructor Destroy;override;
end;

Veja que as variáveis internas da TWCliente são estritamente privadas. Com isso, nem mesmo o TWClienteStatus tem acesso ao estado delas. Por outro lado, o stream _Dados e as funções para alimentá-lo foram incluídas na área protegida de TWClienteStatus, o que dá acesso total para que uma instância de TWCliente armazene aí seu estado. Isso também garante que ninguém fora dessa unit poderá ler, modificar ou gravar qualquer coisa diretamente numa instância do TWClienteStatus, preservando a integridade do estado que ela carrega.

Em TWClienteStatus, há métodos para adicionar valores de diversos tipos de dado ao stream interno:
procedure TWClienteStatus.AdicInfoDbl (Valor: Double);
begin
_Dados.Write(Valor, sizeof(Double));
end;

procedure TWClienteStatus.AdicInfoStr (Valor: String);
var lTamanho, i : integer;
lChr : char;
begin
lTamanho := Length (Valor);
{ Grava o tamanho do texto }
_Dados.Write(lTamanho, sizeof(integer));
{ Grava o texto em si }
for i := 1 to lTamanho do
begin
lChr := Valor[i];
_Dados.Write(lChr, 1);
end;
end;

O armazenamento de dados do tipo string é feito em duas etapas; primeiro, o tamanho do texto é inserido no stream para depois acrescentarmos o texto em si. No TWClienteStatus, os métodos para recuperar cada tipo de valor armazenado seguem essa mesma regra. Isso torna obrigatório fazer na recuperação de dados chamadas rigorosamente equivalentes às feitas durante o salvamento, isto é, a ordem de tipos gravados deve ser a mesma ordem nos tipos recuperados. Os métodos associados à recuperação são os seguintes:
procedure TWClienteStatus.InitGetInfo;
begin
{ Volta o ponteiro do stream para o início dos dados }
_Dados.Position := 0;
end;

function TWClienteStatus.ExtraiInfoDbl : Double;
begin
_Dados.Read(Result, sizeof(Double));
end;

function TWClienteStatus.ExtraiInfoStr : String;
var lTamanho, i : integer;
lChr : char;
begin
Result := '';
{ Obtém o tamanho do texto }
_Dados.Read(lTamanho, sizeof(Integer));

for i := 1 to lTamanho do
begin
_Dados.Read(lChr, 1);
Result := Result + lChr;
end;
end;

A função InitGetInfo reproduzida acima deve preceder a recuperação do estado pois ela faz com que a posição atual do stream aponte novamente o início dos dados que foram salvos. Assim, as funções para salvamento e recuperação do estado do Cliente podem ser escritas como segue:
function TWCliente.SalvaStatus : TWClienteStatus;
begin
Result := TWClienteStatus.Create;

{ Usa as funções protegidas de TWClienteStatus, o que só é permitido porque ambas as classes estão na mesma Unit.}
Result.AdicInfoStr(_Nome);
Result.AdicInfoDbl (_CreditoEmUso);
Result.AdicInfoDbl (_LimiteCredito);
end;

procedure TWCliente.RestauraStatus (Estado: TWClienteStatus);
begin
Estado.InitGetInfo;
_Nome := Estado.ExtraiInfoStr;
_CreditoEmUso := Estado.ExtraiInfoDbl;
_LimiteCredito := Estado.ExtraiInfoDbl;
end;

Neste tipo de solução, algo importante a se planejar é de quem será a responsabilidade por restituir a memória alocada pela função SalvaStatus. No exemplo, ela pode ser encarada como um construtor da classe que mantém estados (TWClienteStatus); portanto, o candidato natural a responsável é quem fez a chamada à função. No nosso caso, deve ser a própria classe de tela, que exerce o papel de Caretaker do Memento:
procedure TWTelaCredito.FormDestroy(Sender: TObject);
begin
FreeAndNil (_Estado);
FreeAndNil (_Cliente);
end;

procedure TWTelaCredito.btnOpCreditoClick(Sender: TObject);
var lValor : Double;
begin
{ Devolve os recursos usados até aqui antes de fazer nova alocação. }
FreeAndNil (_Estado);

try
lValor := StrToFloat (Trim (edNovo.Text));
{ Salva o estado inicial do Cliente }
_Estado := _Cliente.SalvaStatus;

{ Se houve erro na operação, volta ao estado inicial }
if (not _Cliente.RealizaOpCredito(lValor) ) then
_Cliente.RestauraStatus(_Estado);
ExibeCredito;
finally
end;
end;

Embora no nosso exemplo apenas o saldo de crédito seja modificado pela operação aplicada, todos os membros internos do Cliente são salvos, permitindo restaurar o estado anterior a quaisquer operações, não importando quais membros tenham sido alterados.

O projeto do exemplo, construído em Delphi 2010, pode ser baixado neste link.

12 de fevereiro de 2011

Design Patterns com Delphi: Memento – Parte I

Num sistema computacional, são muitas as situações que podem exigir que tenhamos controle sobre o estado atual de um objeto para poder revertê-lo caso alguma alteração feita no estado desse objeto não deva ser levada adiante. O comando "desfazer" (undo), presente em muitos programas, é o exemplo mais comum, mas esse tipo de necessidade também pode ter outras aplicações: o tratamento de rollback (uma das características mais marcantes em sistemas de gerenciamento de banco de dados), em funções de preview (onde alterações podem ser aplicadas temporariamente para que se avalie o impacto que elas terão caso sejam efetivadas) e para trazer de volta o estado original de objetos quando uma situação de erro é encontrada durante uma atualização.

O Design Pattern comportamental Memento foi concebido justamente para ser aplicado nessas situações. A implementação proposta por ele respeita o encapsulamento do objeto envolvido enquanto proporciona-lhe a capacidade de salvar e exportar seu estado num determinado instante e de retornar a qualquer um dos estados salvos no momento em que isso for necessário.

A imagem abaixo é um diagrama UML retratando a relação típica entre as classes participantes da solução para o Memento. Aqui, a ideia é construir uma tela para realizar uma operação de crédito que terá que ser desfeita caso o limite de crédito seja atingido. Nesta condição, um estado previamente salvo pela rotina será restaurado.
Diagrama UML para o padrão Memento

O GoF classifica os participantes nesta solução com a seguinte nomenclatura:
Originator: É a classe capaz de criar um instantâneo do estado de suas variáveis internas, exportando-o de uma forma que permita a restauração desse estado a qualquer momento. Por consequência, a classe também deve ter uma função que aceite como parâmetro um estado previamente salvo para poder fazer com as variáveis internas da instância da classe assumam os valores contidos nesse parâmetro. No diagrama acima, a classe com essas características é a TWCliente. Repare que as variáveis internas da classe TWCliente são mantidas como privadas (private), sendo acessíveis exclusivamente através dos métodos que realizam as operações dela; isto garante o encapsulamento e o baixo acoplamento em relação à classe que utilizará o recurso de salvar/restaurar o estado dos objetos.

Memento: Armazena todas as informações que forem necessárias para que o estado de uma classe do tipo Originator possa ser restaurado com sucesso. No entanto, a relação das informações que devem ser salvas é determinada pela própria classe Originator. Normalmente, a classe Memento inclui mecanismos para barrar a edição de seu conteúdo, preservando as informações que estão salvas nela para garantir que o estado restaurado é rigorosamente o mesmo que foi salvo. Este papel cabe à classe TWClienteStatus no nosso exemplo.

Caretaker: É a classe que utiliza o recurso de salvar/restaurar de um Originator, determinando o melhor momento para que cada etapa aconteça. Ela, portanto, se relaciona com o Originator, salvando-lhe o estado, realizando operações do Originator através da chamada dos métodos que forem convenientes e restaurando-lhe o estado, se e quando isto for necessário. Para isso, o Caretaker sabe como armazenar uma (ou mais) instância(s) de estados do Originator, mas não tem acesso ao conteúdo salvo nem pode modificá-lo. No exemplo, quem detem esse papel é TWTelaCredito.

Há diferenças em relação ao padrão Command, abordado neste outro post. Enquanto no Command definimos uma interface através da qual ações serão executadas, no Memento a interface é substituída por um mecanismo apenas para salvar o estado atual do objeto e para restaurá-lo quando for necessário. Por causa do uso da interface, polimorfismo é parte importante da solução no Command mas irrelevante na adoção do Memento. É claro que podemos planejar a ação inerente a um Command de um modo que ela também possa ser desfeita. Quando isso ocorre, é comum que ambos os padrões sejam postos para trabalhar em conjunto na mesma solução.

Uma curiosidade: memento é um termo que em latim significa "lembra-te". Em português é pouco usado, mas é o nome que se dá a uma caderneta onde se anota coisas que devem ser lembradas.

No próximo post, eu proponho uma implementação em Delphi para o exemplo usado aqui.

Mais Informações
Posts sobre Design Patterns

9 de fevereiro de 2011

Personalizando a edição de propriedades em componentes Delphi - parte II

No último post, eu falei um pouco sobre as preocupações que devemos ter na criação de componentes no Delphi quanto à necessidade de separação do comportamento deles em tempo de execução daqueles comportamentos que são exclusivos do tempo de design – portanto, oferecidos apenas aos programadores.Citei também que a edição de propriedades no Object Inspector é um comportamento de tempo de execução que pode ser customizado pelo programador do componente, bastando criar uma herança da classe TBasePropertyEditor que implemente a interface IProperty.

Dando sequência ao assunto, detalho aqui alguns métodos dessa interface e mostro como criar o editor para uma propriedade que nos permita montar filtros para tipos de arquivos, como o exibido abaixo:
Editor de filtros de tipos de arquivos
Os principais métodos introduzidos pela interface IProperty são listados abaixo:
Edit: Este método é chamado quando se clica o botão de edição apresentado junto à propriedade no Object Inspector. No nosso exemplo, ele criará e exibirá a tela para o programador entrar com os filtros de arquivos desejados.
GetAttributes: Retorna as características da propriedade de forma que o Object Inspector possa determinar quais tipos de controle apresentar ao programador, como botões de edição, caixa com lista para seleção, etc. . Por exemplo, como a propriedade de filtro apresenta uma tela para edição, o Object Inspector deve ser preparado para exibir um botão que dê acesso à tela.
GetEditLimit: Retorna a quantidade máxima de caracteres que o programador pode informar como valor para a propriedade. O nosso exemplo não fará restrições nesse aspecto.
GetValue e SetValue: Respectivamente, obtém o valor atual da propriedade e ajusta um novo valor para a propriedade em tempo de design. Ambas são chamadas automaticamente pelo Object Inspector, desde que a edição da propriedade não se dê através de um diálogo. Como esse é o caso do nosso exemplo, teremos que chamá-las manualmente dentro do método Edit.
GetEditValue: Obtém um texto formatado para apresentar como valor da propriedade em tempo de design. Diferentemente de GetValue e SetValue, essa função não levanta uma exceção caso a propriedade não tenha valor. No nosso exemplo, essa função retornará o mesmo texto que GetValue, já que a propriedade que estamos tratando é do tipo texto.
Para implementar o exemplo com a tela de filtros de arquivos, temos que declarar duas classes. A primeira representa a tela de filtro em si enquanto a segunda tratará os requisitos para intermediar a comunicação com o Object Inspector, isto é, será uma herança de TBasePropertyEditor e implementará a interface IProperty.
{ A tela para edição dos filtros }
TWFilterEditor = class(TForm)
Bevel1: TBevel;
OKButton: TButton;
CancelButton: TButton;
HelpButton: TButton;
procedure FormCreate(Sender: TObject);
procedure HelpBtnClick(Sender: TObject);
private
procedure SetFilter(Value: string);
function GetFilter: string;
end;

{ Classe que gerenciará a comunicação com o Object Inspector }
TWFilterProperty = class(TStringProperty)
public
procedure Edit; override;
function GetAttributes: TPropertyAttributes; override;
end;
Veja que ao criar a classe para comunicação com o Object Inspector eu herdei diretamente da classe da VCL TStringProperty pois ela já implementa grande parte do que precisamos, incluindo os métodos requeridos pela interface IProperty. Aqui, teremos que sobrepor apenas os métodos Edit e GetAttributes para introduzir a parte do comportamento que será diferente para nós:
procedure TWFilterProperty.Edit;
var FilterEditor: TWFilterEditor;
begin
{ Cria e apresenta a tela de filtros }
FilterEditor := TWFilterEditor.Create(Application);
try
FilterEditor.SetFilter(GetValue);
FilterEditor.ShowModal;

{ A edição foi feita com sucesso, ajusta o novo valor para a propriedade }
if FilterEditor.ModalResult = mrOK then
SetValue(FilterEditor.GetFilter);
finally
FilterEditor.Free;
end;
end;

function TWFilterProperty.GetAttributes: TPropertyAttributes;
begin
Result := [paDialog, paMultiSelect];
end;
O método Edit cria uma instância da classe de tela e obtém manualmente o valor da propriedade através do GetValue antes de exibi-la. Ao encerrar a tela após uma edição com sucesso, o valor é novamente atribuído à propriedade, através de uma chamada ao SetValue.

No GetAttributes, retornamos um valor que indica ao Object Inspector que essa propriedade será editada através de uma caixa de diálogo (nossa tela de filtro) e que é permitido ao programador selecionar vários componentes ao mesmo tempo e editar a propriedade em todos eles (paMultiSelect) simultaneamente. Para finalizar, precisamos notificar a VCL sobre qual(is) propriedade(s) deverá(ão) usar nosso editor personalizado:
procedure Register;
begin
RegisterComponents('ABC71', [TWAttachFileBtn]);
RegisterPropertyEditor (TypeInfo(string), TWAttachFileBtn, 'TiposPermitidos', TWFilterProperty);
end;
Observe que o registro do editor para a propriedade ocorre no mesmo local em que o próprio componente é registrado na paleta de componentes do Delphi, isto é, uma função isolada chamada Register criada na própria Unit. Na função acima, nosso editor é associado à propriedade 'TiposPermitidos' de um componente chamado TWAttachFileBtn.

7 de fevereiro de 2011

Personalizando a edição de propriedades em componentes Delphi - parte I

Já comentei em outros posts sobre a criação de componentes em Delphi que programadores que se aventuram nessa seara devem ter em mente preocupações com dois públicos alvos distintos. Em primeiro lugar, definimos a aparência do componente e qual o tipo de interação esperado dele por um usuário final dos programas em que esse componente estará inserido. Isto é, devemos nos preocupar com a forma com que o componente aparecerá para seus usuários e como ele deve se comportar quando utilizado num sistema real.

Por outro lado, é importante também facilitar a vida do usuário mais imediato de um componente: o próprio programador que o usará em design time para montar seus programas. Em Delphi e C++ Builder, essa facilidade se traduz na exposição de propriedades e eventos no Object Inspector, de uma maneira que revela um pouco do funcionamento do componente ao mesmo tempo em que reduz a codificação necessária para que o programador ponha tal componente para funcionar.

Conforme explicato no post Criando Componentes em Delphi - parte II, a forma mais básica de publicar uma propriedade (ou evento) no Object Inspector é declará-la na área pública da classe do componente, através da palavra chave published. Neste caso, a validação do valor informado pelo programador no inspector é feita apenas com base no tipo de dado da propriedade. Isso também determina o modo com que o valor da propriedade será automaticamente anexado ao arquivo DFM do Form onde o componente foi inserido. Observação: um DFM contém os componentes adicionados em tempo de design, descrevendo o estado inicial de suas propriedades em Foms do Delphi.

Mas, nem sempre o básico é suficiente. Imagine, por exemplo, que você está criando um componente no qual uma das propriedades publicadas aceita uma lista com tipos de arquivos permitidos, nos moldes do filtro existente no TOpenDialog. Se tal propriedade for publicada como um texto simples, o programador poderá informar aí qualquer valor, mesmo um que não seja compatível com o formato de lista que definirmos.

Para essas situações, o Delphi permite que o criador de componentes registre editores diferentes do padrão para propriedades onde isso for necessário, evitando (ou até mesmo impedindo) que o programador tenha que informar diretamente o valor desejado em tempo de design. O registro é feito através da função RegisterPropertyEditor, que vincula uma propriedade do componente à classe que deve ser usada para editar seus valores durante o projeto. Essa classe deve ser uma herança de TBasePropertyEditor e deve implementar a interface IProperty. A boa notícia é que para os tipos básicos do Delphi já há classes prontas que satisfazem ambas as exigências, de modo que para montar nosso editor de filtros de arquivos nos bastará derivar uma classe a partir da TStringProperty, que é a classe nativa para edição do valor de propriedades do tipo string (texto) no Object Inspector. A ideia é abrir para o programador uma tela onde ele poderá facilmente estipular em tempo de design a lista de tipos de arquivos considerados pelo componente. A tela apresentada ao programador é reproduzida abaixo:
Editor de filtros de tipos de arquivos
É claro que essa interação é exclusiva para o programador, de modo que a tela para escolha de filtros não é apresentada em nenhum momento ao usuário final da aplicação. Esta é a razão para o Delphi diferenciar os tipos de pacotes: aqueles configurados como Design Time Only devem conter apenas as classes necessárias para o programador fazer seu trabalho, o que as dispensa de serem distribuídas junto com as aplicações que usarem o componente, reduzindo o tamanho dessas aplicações.

A implementação do comportamento inerente ao componente deve permanecer em pacotes do tipo Run Time Only, imprescindíveis para o funcionamento tanto do executável final quanto das interações em tempo de design.

No próximo post, apresento os métodos que merecem atenção quando criamos nossas próprias classes de editores e também mostro a construção de uma propriedade com as características do filtro de arquivos citada acima.