30 de setembro de 2009

Quantidade de usuários testando o Google Wave será ampliada

Nesta quarta-feira, dia 30 de setembro de 2009, a Google enviará convites para aceitar novos usuários de teste de sua nova plataforma de comunicação e colaboração, o Google Wave. Serão distribuidos 100 mil convites para novos usuários aderirem ao serviço e desta vez o Google não restringirá o acesso a desenvolvedores de software, dando oportunidade a usuários comuns também fazerem suas avaliações.

De acordo com a Info Online, o serviço ainda está instável e imaturo, apresentado falhas e sem algumas funcionalidades básicas. Aparentemente, o objetivo da Google é mesmo saber como a ferramenta se comporta com uma quantidade maior e mais diversificada de usuários, já que hoje apenas 3000 programadores tem acesso. Ao incluir usuários comuns na base de teste, a Google também abre um leque maior de visões e opiniões distintas sobre o produto, o que deve aumentar-lhe as funcionalidades e certamente melhorará o resultado final.

E quem serão os felizardos que receberão os convites para ingressar no Google Wave ? Um post incluido no Blog Oficial do Google dá algumas pistas a esse respeito. Reproduzo abaixo uma tradução desse post; a versão original em inglês pode ser acessada aqui.


A partir de 30 de Setembro, estaremos enviando mais 100 mil convites para o preview do Google Wave. Os contemplados serão :
* Desenvolvedores que foram ativados no Preview para Desenvolvedores iniciado em junho;
* Os primeiros usuários que se cadastraram no wave.google.com e que se dispuseram a fornecer feedbacks;
* Clientes selecionados do Google Apps

Solicitaremos a alguns desses usuários que indiquem pessoas que eles conheçam para receber convites — o Google Wave é muito mais útil se seus amigos, parentes e colegas o tiverem também. Isto, é claro, será apenas o início. Se tudo correr bem, logo estaremos convidando muitos mais.

Alguns perguntaram qual o significado de 'preview'. Significa apenas que o Google Wave não está totalmente finalizado. Ainda não, de qualquer modo. Desde que revelamos o projeto em maio, nos focamos quase exclusivamente em escalabilidade, estabilidade, velocidade e usabilidade. Mesmo assim, ocasionalmente vocês ainda verão o sistema fora do ar, erros fatais uma vez ou outra, partes do sistema com lentidão e a interface gráfica, bem, fora do convencional.

Ainda há funções chaves no Google Wave que precisam ser totalmente implementadas. Por exemplo, vocês ainda não poderão remover um participante de uma "wave" ou definir grupos de usuários, o modo rascunho ainda está ausente e não é possível configurar permissões dos usuários numa "wave". Disponibilizaremos essas e outras funcionalidades tão logo elas estejam prontas, ao longo dos próximos meses.

Apesar disso tudo, acreditamos que vocês acharão que o Google Wave tem potencial para torná-los mais produtivos em termos de comunicação e colaboração. Mesmo quando estiver apenas se divertindo ! Nós usamos o Google Wave no dia-a-dia para tudo, desde planejar a cervejada até compartilhar fotos, gerenciar processos e debater funções para um projeto. De fato, houve colaboração até na criação deste post, com a participação de diversos colegas no Google Wave.

Falando sobre as formas potenciais de uso do Google Wave, estamos intrigados com algumas muito detalhadas que algumas pessoas tiveram tempo de descrever. Para mecionar algumas: o jornalista Andy Ihnatko na produção de sua coluna no Chicago Sun-Times, o produtor Jonathan Poritsky ao detalhar o fluxo de produção de um filme, o cientista Cameron Neylon sobre artigos acadêmicos e trabalho laboratorial, Alexander Dreiling e sua equipe na SAP pesquisando sobre modelagem de processos de negócio colaborativos, e o caso de uso em empresas feito por Dion Hincliffe da ZDNet.

O dia mais divertido para a equipe do Wave desde maio ? Convidamos um grupo de estudantes para passar um dia conosco no escritório do Google em Sydney. Entre outras coisas, pedimos a eles que escrevessem colaborativamente estórias no Google Wave sobre uma viagem imaginária ao redor da Terra. Adoraram! Como nós...

Por fim, chamo a atenção dos milhares de desenvolvedores que pacientemente tomaram parte do preview para desenvolvedores, em andamento. Tem sido divertido ver as extensões legais já construidas ou sendo planejadas e incrivelmente instrutivo obter a ajuda deles para planejar o futuro de nossa API. Para ter uma prévia sobre em que alguns desses criativos desenvolvedores vêm trabalhando e aprender mais sobre as maneiras como nós esperamos tornar mais fácil o desenvolvimento de novas extensões, vejam esse post no nosso blog para desenvolvedores.

Boa onda!


28 de setembro de 2009

Softwares para virtualização de computadores

Mesmo com a queda dos preços dos componentes de computadores, como memória e disco rígido, a virtualização de hardware tem sido uma boa opção para empresas reduzirem custos. A ideia é simples : compra-se um único computador potente o bastante para comportar a emulação de diversas máquinas, com a possibilidade de simular diversos ambientes diferentes. Ou seja, paga-se por uma máquina e pode-se ter diversas outras pelo mesmo preço.

Há diversos softwares no mercado para virtualização tanto de servidores quanto de desktops mas acredito que os mais comuns sejam o Virtual PC da Microsoft e o VMWare da empresa de mesmo nome. Ambos possuem versões gratuitas para virtualização do Desktop.

Em ambos os casos, a máquina real tem seu próprio sistema operacional, que é onde o software de virtualização executará. As máquinas virtuais, então, são carregadas e emuladas dentro do software de virtualização. Elas compartilham o hardware da máquina real, isto é, usam a mesma mémoria, o mesmo disco rígido, rede, impressoras que estejam instaladas, etc - daí a necessidade do computador real ser robusto, caso contrário as máquinas virtuais podem se tornar tão lentas que deixa de ser interessante a virtualização.

A grosso modo, a máquina ou computador virtual é apenas um grande arquivo cujo conteúdo simula a estrutura existente num disco rígido de uma máquina real. O software de virtualização fornece, através de drivers internos, maneiras do sistema operacional interagir com essa estrutura e com o resto do hardware. Assim, o sistema operacional que está executando na máquina virtual é levado a crer que trabalha com uma máquina convencional.

Uma peculiaridade decorrente dessa forma de trabalhar é que as máquinas virtuais permitem que se salve o contexto de trabalho. Isto é, você pode modificar arquivos, realizar instalações, modificar configurações, etc. e só aplicar as alterações se julgar conveniente. Se não gravá-las, a próxima vez que entrar na máquina virtual, você irá encontrá-la exatamente no mesmo estado que se encontrava antes, isto é, com os dados efetivados pela última gravação que você tenha feito. Na ABC71, por exemplo, eu uso o Virtual PC da Microsoft para fazer testes quando há alterações no programa instalador do nosso ERP. Realizo a instalação, vejo se os arquivos foram copiados corretamente, se as chaves no Registry foram criadas e estão íntegras, se o banco de dados foi populado com os registros iniciais necessários, etc. Após o teste, saio sem gravar as alterações na máquina virtual e ela estará pronta para um novo teste no futuro, sem apresentar traços dos testes de instalação anteriores.

A aplicação desse tipo de software é vasta e não se restringe a poupar dinheiro com a compra de máquinas. Além do uso para instalação de softwares como citado acima, há outras possibilidades de uso interessantes:
Num ambiente de treinamento onde há turmas em períodos distintos usando a mesma sala e mesmos equipamentos, máquinas virtuais distintas para cada aluno separa o estado de cada computador, evitando interferências do ambiente de um aluno no ambiente do outro. Se por acaso for o mesmo curso, evita até que um "cole" do outro.
Quando é necessário ter ambientes heterogênios para testes. Por exemplo, analisar o comportamento de uma aplicação em sabores diferentes do próprio Windows (XP, Vista, 98, etc) Como é possível instalar sistemas de outros fabricantes - como Linux - o caminho fica aberto para testar aplicações Web em browsers de sistemas operacionais diversos e, também, para validar aplicações .NET Multiplataforma.
Para usar aplicações legadas, isto é, programas que só rodam em uma versão antiga do sistema operacional. Isto permite que se atualize a versão do Windows num computador sem correr o risco de ter que deixar de usar a aplicação em questão.
Há certas aplicações que não conseguem trabalhar direito quando uma outra determinada aplicação também está instalada. Seja porque teriam que compartilhar algum recurso do computador (uma versão distinta de um driver ou de um arquivo) ou porque exijam uma configuração mutuamente exclusiva. Um caso típico são aplicações Java que precisam de uma versão distinta do JVM para funcionar.

Além disso tudo, como todas as máquinas virtuais se encontram numa mesma máquina real, é mais fácil gerenciá-las. E, como as máquinas em si se restrigem ao arquivo que representam seu conteúdo, fica mais fácil fazer backups.

25 de setembro de 2009

Design Patterns com Delphi : Composite

Muitas situações do mundo real podem ser modeladas num sistema computacional como uma estrutura de objetos em forma de árvore, num relacionamento todo-partes. Isto é, uma classe de objetos é formada por outras classes (e estas classes também podem ser compostas por outras), configurando uma hierarquia em árvore entre os objetos envolvidos.

Às vezes, nesse tipo de hierarquia há certas operações aplicáveis ao objeto "todo" que podem também fazer sentido se aplicadas às partes isoladamente. Um exemplo clássico desse cenário é ter um objeto Desenho composto por elementos gráficos como linhas, círculos, triângulos, etc. O Desenho terá operações como "pintar-se", "redimensionar-se" e "mover-se" e estas mesmas operações podem ser aplicadas a cada uma das partes de forma independente.

O objetivo do Design Pattern estrutural Composite é justamente esse : permitir que classes Clientes possam tratar objetos individuais ou composições desse objeto de forma uniforme, podendo aplicar operações no todo ou em partes dele sem se preocupar se o objeto é de um tipo ou de outro. Um exemplo aplicável num ERP como o da ABC71 é a ideia de contratos. Os Contratos são compostos por Cláusulas e tanto o Contrato quanto suas Cláusulas individuais podem ser alteradas, revogadas ou perder validade.

Veja o diagram UML para esse Pattern.
Diagrama UML para Composite

A nomenclatura para as classes participantes de uma solução do tipo Composite é a seguinte :
O Component é quem declara a interface comum a todos os objetos da composição. Ele também implementa a parte dessa interface que fizer sentido para todos os objetos da composição, isto é, o Component não precisa ser uma classe abstrata pura. A interface ainda introduz métodos para gerenciar filhos, isto é, a coleção de partes que compõem o todo. No diagrama acima, esse papel cabe à classe TWClausulaContrato.
O Composite propriamente dito implementa todo o comportamento relacionado aos Components que têm filhos, permitindo gerenciar a lista de filhos e introduzindo outras operações que façam sentido nesse contexto. No exemplo, quem exerce esse papel é a classe TWContrato, que poderá ter uma lista de cláusulas como filhos.
Os Components que são elementos primitivos, isto é, não têm filhos, são denominados Leaf (folhas). No caso dos contratos, uma cláusula (classe TWClausula) é um elemento folha pois não é permitido a ele ser composto por outras cláusulas. Note que da forma como está projetado, um contrato pode ser composto por outros contratos e cláusulas. Assim, posso ter modelos de contratos com cláusulas específicas e moldar o contrato principal como uma coleção desses modelos, tendo ainda a flexibilidade de adicionar cláusulas avulsas.
Por fim, o Client é qualquer parte da aplicação que faça chamadas a operações do Component. No diagrama, esse é o papel da classe TWOperação.

O mapeamento desse padrão usando Delphi é feito através de heranças simples. Para facilitar o tratamento de lista na classe Composite, pode-se usar internamente a própria classe TList da VCL.
type
TWClausulaContrato = class
{ ... }
procedure Cancelar;virtual;
procedure Aplicar;virtual;
function EstaValido : boolean;virtual;

procedure Add (AElem : TWClausulaContrato);virtual;abstract;
procedure Remove (AElem : TWClausulaContrato);virtual;abstract;
function ItemAt ( AIndex : Integer) : TWClausulaContrato;virtual;abstract;
end;

TWClausula = class(TWClausulaContrato)
{ ... }
procedure Cancelar;override;
procedure Aplicar;override;
function EstaValido : boolean;override;

procedure Add (AElem : TWClausulaContrato);override;
procedure Remove (AElem : TWClausulaContrato);override;
function ItemAt ( AIndex : Integer) : TWClausulaContrato;override;
end;

TWContrato = class(TWClausulaContrato)
{ ... }
_Clausulas : TList;
procedure Cancelar;override;
procedure Aplicar;override;
function EstaValido : boolean;override;

procedure Add (AElem : TWClausulaContrato);override;
procedure Remove (AElem : TWClausulaContrato);override;
function ItemAt ( AIndex : Integer) : TWClausulaContrato;override;
end;

Para a classe TWClausula, as funções de gerenciamento não fazem nada - embora seja comum classes Leaf reportarem mensagens de erro para reforçar que elas não comportam filhos.
procedure TWClausula.Add (AElem : TWClausulaContrato);
begin
end;

procedure TWClausula.Remove (AElem : TWClausulaContrato);
begin
end;

function TWClausula.ItemAt ( AIndex : Integer) : TWClausulaContrato;
Result := Nil;
end;

Para a classe TWContrato, uso o TList para armazenar os filhos, sem importar se o novo elemento é uma cláusula simples ou um contrato mais complexo.
procedure TWContrato.Add (AElem : TWClausulaContrato);
begin
_Clausulas.Add (AElem);
end;

procedure TWContrato.Remove (AElem : TWClausulaContrato);
begin
_Clausulas.Remove (AElem);
end;

function TWContrato.ItemAt ( AIndex : Integer) : TWClausulaContrato;
Result := TWClausulaContrato(_Clausulas.Items[AIndex]);
end;


Mais Informações
Posts sobre Design Patterns

22 de setembro de 2009

Acrescentando suporte a Scripts em aplicações Delphi - parte 5

Prosseguindo o assunto iniciado no post anterior, vou mostrar aqui o código para fazer funcionar a manupulação de variáveis do meu programa dentro dos scripts.

A primeira providência é criar uma estrutura com as informações que poderão ser manipuladas, isto é, o saldo e a cor do Form. O Form então manterá uma instância dessa estrutura e poderá passá-la para a interface que criei no outro post usando para isso a função InitData:
{ A estrutura ...}
DoublePtr = ^Double;
TWDados = record
Saldo : DoublePtr;
ParentForm : TForm;
end;
TWDadosPtr = ^TWDados;

Veja que criei o Saldo como um ponteiro para Double. A intenção é que esta estrutura sirva de ponte para variáveis reais do meu programa. Assim, quando o script referenciar o Saldo estará referenciando exatamente a mesma variável que existe internamente no programa.

Agora, o Form pode criar uma instância de classe que implementa a nossa interface e transferir para ela a estrutura alimentada. O IDE do Delphi monta automaticamente uma classe (a CoClass) que cria tal instância, ligando a interface a uma classe concreta, que no nosso caso é a TExemplo, cujo esqueleto também foi montado pelo IDE do Delphi.
{ A classe TExemplo }
TExemplo = class(TAutoObject, IExemplo)
private
_Dados : TWDadosPtr;
{ ...}
end;

{ No construtor do Form }
_Saldo := 0;
_Dados.Saldo := @_Saldo;
_Dados.ParentForm := Self;

_VarExemplo := CoExemplo.Create;
_VarExemplo.InitData(@_Dados);
{ ...}

Observe a declaração da classe TExemplo : ela é uma herança do TAutoObject (um objeto COM para automação, definindo o programa como um COM Server) e também implementa a interface IExemplo, com as propriedades e métodos que planejamos permitir interação através de scripts. Como ela é a classe concreta para a interação, inclui nela um membro do tipo TWDadosPtr para receber a estrutura que é mantida pelo Form.

Ao preencher os esqueletos de métodos gerados pelo Delphi para a classe concreta TExemplo, usamos a estrutura informada pelo InitData. Por exemplo:
procedure TExemplo.InitData(AInfo: PChar);
begin
_Dados := TWDadosPtr (AInfo);
end;

function TExemplo.Get_CorForm: Integer;
begin
Result := integer (_Dados.ParentForm.Color);
end;

function TExemplo.Get_Saldo: Double;
begin
Result := _Dados.Saldo^;
end;

O que a função CoExemplo.Create retorna é um ponteiro para a interface e não para a classe concreta. Por isso, _VarExemplo deve ser declarada no Form como sendo do tipo interface.
private
_VarExemplo: IExemplo;

Só falta incluir a variável do programa no engine do Script Control para permitir que ela seja acessada por scripts. Para isso, basta usar o método AddObject.
_ScriptControl.AddObject('Vars', _VarExemplo, true);

O nome Vars passa a representar a instância de nossa interface nos scripts. Então, o código abaixo é reconhecido como válido para um script executado por nosso programa:
Vars.Saldo = Vars.Saldo + 1
Vars.CorForm = RGB (0, 255, 0)

O programa completo com os exemplos incluídos nos posts sobre Script com Delphi (incluindo o executável, os fontes e a type library) pode ser baixado neste link.

21 de setembro de 2009

Acrescentando suporte a Scripts em aplicações Delphi - parte 4

Comecei uma série de posts em Agosto para mostrar como fazer com que uma aplicação Delphi permita interação com o usuário através de scripts usando o componente COM da Microsoft chamado Script Control. Ficou faltando falar sobre a publicação de variáveis do programa no engine no Script Control, o que na prática dará ao usuário acesso a essa variável. Num primeiro momento, pode parecer um risco permitir tal acesso mas pense na liberdade para o usuário customizar fórmulas dentro da aplicação, por exemplo. Dependendo do objetivo, é possível planejar uma estrutura em que o usuário poderá até mesmo customizar o funcionamento e a aparência de funções inteiras da aplicação.

Na verdade, a publicação tem que ser feita através de interfaces o que significa que você publicará o objeto inteiro que implementar tal interface. No entanto, pelas características do funcionamento das interfaces, o script enxergará apenas o que você desejar - os métodos e propriedades declarados na tal interface.

O primeiro passo, portanto, é planejar o quê o usuário poderá manipular. Vou estender o exemplo que usei nos outros posts e acrescentar uma interface que permita ao usuário manipular um saldo e a cor do form. Como as duas informações não têm qualquer relação entre si, o ideal seria colocá-las em interfaces distintas. Mas como se trata de um exemplo ...

Para criar a interface da forma que o COM precisa teremos que descrevê-la em uma Type Library. Uma Type Library define tipos de dados usando uma sintaxe que independe de linguagens de programação. Um IDE como o Delphi é capaz de ler essas informações e gerar automaticamente classes para lidar com o componente COM que a Type Library descreve. Uma aplicação ou biblioteca que implemente a interface nesses termos é classificada como um Servidor COM (COM Server) pois ela é capaz de suprir as chamadas dirigidas à interface.

Nosso objetivo, então, é transformar a aplicação num servidor COM e a forma mais prática de fazer isso em Delphi (e C++ Builder) é criando um Automation Object. Com o projeto da aplicação aberto, escolha o menu File, opção New e Other. No diálogo que aparece, localize a opção Active X e, finalmente, Automation Object. O IDE se encarregará de criar uma Type Library e uma classe capaz de criar uma instância da classe concreta (chamada de CoClass) que implementa nossa interface. Também criará código para ligar isso tudo. Dei o nome de Exemplo quando o IDE pediu :
Criação do Automation Object

No editor de Type Library, acrescentei à interface IExemplo as propriedades relativas aos valores que escolhemos anteriormente para publicação : o saldo e a cor do Form. Veja na reprodução abaixo que há também uma função com o nome de InitData.
Type Library Exemplo

Quando trabalho com interfaces COM eu tenho problemas em obter o ponteiro para a classe original e fazê-la interagir com outras classes do programa - é o mesmo tipo de problema enfrentado quando se trabalha com interfaces no Delphi. Por essa razão, introduzi a função InitData com um parâmetro PChar, que é um ponteiro genérico. Desse jeito, mesmo que o programa só consiga enxergar as propriedades e métodos definidos na interface, eu posso passar à classe que implementa essa interface um ponteiro para uma estrutura interna minha, com os valores que eu quero permitir interação. Essa estrutura pode até ser uma classe minha ou todo o Form Delphi, se for necessário.

Mas tudo que está numa interface não é público ? Desse jeito, alguém com más intenções não pode passar um lixo qualquer nessa função através do script e derrubar a aplicação ? Pode sim. Para evitar isso, marquei na guia Flags do editor de Type Library as caixas Hidden e Restricted. Com isso, apenas o meu programa consegue enxergar a função InitData, restringindo o acesso quando a chamada parte de um Client COM qualquer.

Agora que tenho uma interface COM, falta preencher o código por trás dela (a CoClass) e permitir que o Script Engine embutido na aplicação enxergue uma variável que a implemente. Faço isso no próximo post e publico o programa de exemplo.

Mais Informações
Acrescentando suporte a Scripts em aplicações Delphi - parte 1, parte 2 e parte 3, Interfaces com Delphi.

18 de setembro de 2009

Desenvolvimento de Aplicações Flash estendida a desenvolvedores HTML

Em julho eu escrevi um post comentando a proximidade do lançamento do padrão HTML 5, a briga para definir o codec de áudio e o de vídeo e a perspectiva de que o novo padrão pudesse por fim ao desenvolvimento de aplicações Flash e Silverlight, como especialistas vêm divulgando.

Vi uma notícia essa semana no site da InfoWorld de que a Adobe vem se mexendo para não perder terreno por conta do novo padrão. Eles estão lançando um novo produto que mira nos desenvolvedores que já conhecem HTML para que eles possam usar esse conhecimento na construção de aplicações Flash. A ideia do produto, chamado Fluid HTML, é simples mas eficaz para quem já conhece HTML. Ele permite que se crie um site ou uma aplicação em Flash usando o conceito de tags, como no HTML.

Reproduzo abaixo uma tradução da notícia, cuja versão original em inglês pode ser lida neste link.

Programadores de computador serão capazes de usar suas habilidades de desenvolvimento em HTML para construir aplicações baseadas na tecnologia Flash da Adobe, que por sua vez permite criar aplicações de internet ricas (RIA - Rich Internet Applications). Esse aproveitamento se dará através de uma nova tecnologia, o FHTML.

Lançado esta semana, o produto FluidHTML da Adobe funciona como uma linguagem parecida com o HTML tradicional - um texto com marcações, as tags - e permitirá a criação de Web sites e aplicações em Flash, disse na terça-feira Michael Collette, CEO da FHTML. As aplicações são renderizadas na tela pelo plug-in do Flash, diz ele.

"É relativamente simples ampliar suas habilidades", diz Collette. De acordo com o site do FHTML, qualquer um consegue construir "web sites altamente animados, anúncios, tocadores de áudio/vídeo e animações 3D rapidamente e com facilidade."

O FluidHTML serve como uma alternativa à linguagem Flex da Adobe mas, ao contrário do Flex, as aplicações com o FluidHTML não são compiladas. Ao invés disso, um interpretador em tempo real é usado, o que é uma vantagem quando se produz conteúdo no qual se queria permitir pesquisa, diz Collette. Deep-linking também é permitido. Isto permite que se vincule um catálogo a um determinado banner com propaganda, por exemplo.

O plano de negócios do FHTML pretende habilitar o desenvolvimento gratuito de aplicações, com uma taxa a ser cobrada apenas na entrega da aplicação construída. A tecnologia entra numa fase de teste limitada no mês que vem, com disponibilidade geral planejada para acontecer em Dezembro.

Os preços para a entrega de aplicações varia entre $99 (pequenos sites) e $499 (para sites com até 50.000 visitas por mês). Um preço por empresa também será oferecido.


Mais Informações
Mais sobre o HTML 5, Fluid HTML

16 de setembro de 2009

Design Patterns com Delphi : Bridge

Uma situação bastante comum quando se desenvolve programas complexos como um ERP é você ter um tipo de objeto associado a uma família de objetos de mesma interface e essa associação só ser resolvida em tempo de execução. Por exemplo, você tem objeto de negócio que pode ser exportado para arquivos em vários formatos mas o formato que vai ser usado de fato só é definido na última hora.

Se a classe desse objeto de negócio for base para outras classes, o nível de complexidade pode aumentar consideravelmente e tornar difícil a manutenção do código. Essa situação é abordada pelo Design Pattern estrutural Bridge, que estipula que se separe a abstração de uma funcionalidade de sua implementação. No exemplo citado, a abstração é a possiblidade de exportação dos dados do objeto enquanto são exemplos de implementação classes que exportem de fato para um formato, como XML, Excel ou CSV. Veja o diagram UML para esse Pattern.
Diagrama UML para Bridge

A nomenclatura para as classes participantes de uma solução do tipo Bridge é a seguinte :
A Abstraction é quem publica a interface para a funcionalidade que se está abstraindo, de modo que um cliente qualquer possa acessá-la sem se preocupar com detalhes da implementação. A abstração mantem internamente uma referência à implementação real (concreta) da funcionalidade. No diagrama acima, o papel de Abstraction é representado pela classe TWBusinessObj.
O RefinedAbstraction representa uma herança da Abstraction original. Como a implementação da funcionalidade é desacoplada da abstração, é possíver evoluir a abstração básica através de heranças de forma independente. A classe TWFatura no diagrama anterior é um exemplo desse desacoplamento.
O Implementor define a interface padrão para a implementação real da abstração. Esta interface pode ser completamente diferente daquela oferecida pela abstração a seus clientes, tornando possível reutilizar o Implementor em outras abstrações sem que uma interfira na outra. No exemplo, este papel cabe à classe TWExportable.
Cada ConcreteImplementor implementa uma solução específica para a interface publicada pelo Implementor, permitindo à abstração se beneficiar de novas implementações sem se que seja necessário modificá-la. No diagrama, TWExportableCSV e TWExportableXML ilustram esse papel.
O Client é uma classe externa que faz uso da abstração e, portanto, não tem conhecimento de como a abstração está de fato implementada. TWOperacao representa esta classe externa no diagrama acima.

Da mesma forma que no padrão Adapter, o Bridge também precisa estar associado a um pattern criacional como o Factory Method ou o Abstract Factory para resolver a questão de qual classe concreta deve ser instanciada em cada situação. Isso vale tanto para a Abstraction quanto para o Implementor.

Em Delphi, a implementação do Bridge usa herança e composição para criar as associações necessárias. Nas classes Implementor:
type
TWExportable = class
{ ... }
procedure InitExport;virtual;abstract;
procedure GravaCampo (pCampo : TWField);virtual;abstract;
procedure TermExport;virtual;abstract;
end;

TWExportableXML = class(TWExportable)
{ ... }
procedure InitExport;override;
procedure GravaCampo (pCampo : TWField);override;
procedure TermExport;override;
end;

TWExportableCSV = class(TWExportable)
{ ... }
procedure InitExport;override;
procedure GravaCampo (pCampo : TWField);override;
procedure TermExport;override;
end;

Em relação ao Abstraction, também há a hierarquia de classes com herança. Além disso, há a presença de uma propriedade com o Implementor, o que determina a composição.
type
TWBusinessObj = class
{ ... }
FImpl : TWExportable;
procedure Exporta;virtual;
end;

TWFatura = class(TWBusinessObj)
{ ... }
procedure Exporta;override;
end;

Por fim, a classe cliente TWOperacao pode fazer uso do método Exporta sem se preocupar com a implementação dele :
procedure TWOperacao.FazExportacao;
var obj : TWBusinessObj;
begin
{ ... }
obj := GetBusinessObjFromFactory (toFatura);
obj.Exporta;
end;

A diferença entre esse pattern e o pattern Adapter é sutil. No Adapter você tem uma classe preexistente e precisa ligá-la a outra classe que trabalha com uma interface diferente. O efeito é conseguido através da criação de uma nova classe que serve para mapear as chamadas a funções da classe preexistente para obter o resultado desejado. Como ambas as classes (a que mapeia as funções e a que tem as funções chamadas) são muito acopladas, ambas devem evoluir juntas. Ou seja, ao introduzir novas funcionalidades, ambas tem necessariamente que ser alteradas.

No caso do Bridge, as classes envolvidas são desacopladas e podem evoluir independentemente. Isto é, novas funcionalidades em qualquer uma delas não implica necessariamente em alterações em ambas. Portanto, no Bridge a abstração (o que se espera que uma classe faça) é separada da implementação (como a classe faz o que se espera dela).

Mais Informações
Posts sobre Design Patterns

14 de setembro de 2009

Trabalhando com imagens de CDs e DVDs

Se você tem algum CD ou DVD de software, você provavelmente já se perguntou o que é que vai fazer no caso de acontecer algum problema com ele. Ele pode se riscar, se desgastar ou você pode simplesmente perdê-lo.

Claro que é sempre possível - e recomendável - "queimar" uma cópia dele numa outra mídia e usá-la para evitar que problemas ocorram com a mídia original. Mas há uma solução muito mais elegante : um software chamado Daemon-Tools.

O objetivo primário do Daemon-Tools é simular a existência de um leitor de CD ou DVD, permitindo que você use um leitor virtual de discos. O funcionamento básico é o seguinte : você cria uma imagem da midia (CD/DVD/Blue Ray) no seu disco rígido e comanda o Daemon-Tools para que ele abra no leitor virtual a imagem criada, numa operação que ele chama de "montagem". A imagem da mídia nada mais é que um arquivo para onde o conteúdo do disco original foi transferido e que é organizado num formato conhecido. Este arquivo usualmente tem a extensão ISO mas o Daemon Tools é capaz de "montar" imagens em diversos formatos, incluindo alguns proprietários : ISO, CUE, MDS, NRG, CCD, CDI, etc.

O programa não tem interface visual, aparecendo apenas como um ícone na barra do Windows. É a partir desse ícone que se configura e se opera o programa, ajustando a quantidade de leitores virtuais que estarão ativos e permitindo montar os discos. Do ponto de vista do usuário do Windows, o leitor virtual de discos é exibido no Windows Explorer da mesma maneira que os leitores reais, de forma que é impossível distinguir entre uns e outros.

As vantagens de se usar apenas a imagem do disco no lugar da mídia original são inúmeras. Além da óbvia citada no primeiro parágrafo - fazer cópia de segurança da mídia original - você evita o desgaste natural decorrente do uso constante; pode utilizar o CD/DVD/Blue Ray num computador que não possua leitor físico para esses tipos de mídia (redução de custos) além do fato de que a execução a partir do disco rígido é mais rápida do que executar diretamente do CD. E você não precisa manipular a mídia física, o que é mais prático e minimiza o risco de perdê-la.

Há duas versões da ferramenta. Uma, gratuita e voltada ao uso não-comercial, permite a montagem de até 4 discos virtuais simultaneamente, sendo que a quantidade de leitores que estarão de fato disponíveis é determinada pelo usuário. Essa versão não permite criar as imagens de discos. Para isso, é preciso ter outro programa, como por exemplo, o Nero - que costuma vir com gravadores de CD/DVD - ou o ISO Recorder, que é gratuito.

A versão professional é paga mas também permite que você crie imagens de CDs, DVDs e até Blue-Rays que estejam inseridos em leitores físicos do seu computador. Nesta versão é permitido operar até 32 discos simultaneamente.

11 de setembro de 2009

Obtendo nível de sinal do Wifi usando WMI com Delphi

O WMI - Windows Management Instrumentation - é um serviço do Windows que permite o gerenciamento centralizado de informações e operações do Sistema. Isso se dá através da publicação de uma série de interfaces padronizadas com as quais é possível obter informações sobre o hardware do computador, sobre o Registry, status dos Serviços e uma infinidade de outros recursos do Windows. Fiz uso da infraestrutura do WMI quando mostrei aqui no blog como montar um script para manusear Serviços do Windows. Naquela ocasião, usei VBScript; neste post, usarei o Delphi para acessar o WMI, montando uma função que pesquisa o nível (força) do sinal WiFi que está chegando na NIC.

O funcionamento das interfaces do WMI se parecem com o acesso a um banco de dados, onde você pode submeter queries nas tabelas que detem as informações que se quer recuperar sobre o Sistema Operacional. Uma lista das "tabelas" (na verdade são classes) existentes pode ser encontrada no site para desenvolvedores da Microsoft.

A Microsoft disponibiliza o acesso ao WMI através de COM de modo que é possível realizar tarefas de gerenciamento até mesmo com scripts. Para usá-lo no Delphi ou C++ Builder, é preciso importar a type library correspondente cujo nome é Microsoft WMI Scripting V1.2 Library - o número da versão pode variar, dependendo do que você tem instalado em seu computador.

O primeiro passo é criar uma instância de classe que implemente a interface ISWbemLocator. Essa interface é responsável pela conexão com o serviço WMI, como no exemplo abaixo.
var Locator : ISWbemLocator;
Services: ISWbemServices;
begin
Locator := CoSWbemLocator.Create;
Services := Locator.ConnectServer('.', 'root\wmi','', '', '','', 0, Nil);

A função ConnectServer permite também a conexão com o WMI de outro computador, bastando informar qual é esse computador e as credenciais de acesso (usuário e senha). No exemplo, estou conectando ao WMI do meu próprio computador, representado pelo nome '.'. O valor 'root\wmi' aponta o namespace onde está a informação que eu quero; consulte a documentação para mais detalhes sobre os namespaces. O que essa função retorna é a interface ISWbemServices, responsável por providenciar acesso às tarefas disponíveis no WMI.

No caso tratado por este post, a tarefa é apenas a execução de uma query para recuperar o nível do sinal WiFi percebido pelo computador. O nome da tabela (ou classe) que tem essa informação é a MSNdis_80211_ReceivedSignalStrength e por isso vou montar o SELECT nela:
var ObjSet: ISWbemObjectSet;
begin
ObjSet := Services.ExecQuery(
'SELECT * FROM MSNdis_80211_ReceivedSignalStrength',
'WQL', wbemFlagReturnImmediately , nil);

A função ExecQuery retorna uma espécie de record set, isto é, uma coleção de registros que atendem a query especificada. No caso aqui, será retornado um valor para cada placa de rede encontrada. Esse record set implementa a interface IEnumVariant para percorrer todos os "registros" que forem retornados pela query. Assim, posso obter o valor da propriedade Ndis80211ReceivedSignalStrength de cada um deles.
var SObject: ISWbemObject;
SProp: ISWbemProperty;
Enum: IEnumVariant;
Value: Cardinal;
ObjRet: OleVariant;
begin
Enum := ObjSet._NewEnum As IEnumVariant;
while (Enum.Next(1, objRet, Value) = S_OK) do
begin
SObject := IUnknown(objRet) As ISWBemObject;
SProp := SObject.Properties_.Item('Ndis80211ReceivedSignalStrength', 0);
if not VarIsNull(SProp.Get_Value) then
Result := String (SProp.Get_Value) + #13#10;
end;

A propriedade Ndis80211ReceivedSignalStrength é tratada como se fosse um campo numa tabela no banco de dados e é ela que tem de fato o nível do sinal. Note, no entanto, que o valor retornado por ela não é fixo. Cada vez que a função for executada, o valor reportado será o nível do sinal percebido naquele momento. O número retornado é expresso em DBMs e pode assumir valores entre -50 (sinal mais forte) e -100 (sinal mais fraco).

No trecho de código acima, o cast String (SProp.Get_Value) só funciona porque o valor retornado por essa propriedade é conversível para String. Outras propriedades podem ter tipos diferentes e exigirão casts diferentes. Isso inclui até objetos, caso em que a conversão terá que ser feita de outra maneira - talvez uma interface específica.

O acesso ao WMI utilizando a versão .NET do Delphi é ligeiramente diferente. Nesta plataforma, o ObjSet não é compatível com a interface IEnumVariant e sim com a IEnumerator. Portanto, todo o laço onde é feita a navegação pelos registros tem que ser revisto para poder funcionar no .NET.

Esse tipo de abordagem com o WMI não funciona no Windows Vista. Veja este artigo, publicado no site Technet da Microsoft.

Mais Informações
Documentação do WMI

8 de setembro de 2009

Design Patterns com Delphi : Adapter

Com o avanço do uso de componentes na computação tem sido cada vez mais comum desenvolver programas que nada mais são que uma colagem de bibliotecas de terceiros. Além dos IDEs de ferramentas como Delphi, VB.NET e C# direcionarem o desenvolvimento de software por componentização, vem crescendo também a quantidade de desenvolvedores criando bibliotecas de software livre.

Muitos componentes, no entanto, vão se tornando obsoletos conforme evoluem os sistemas operacionais, as técnicas de programação e mesmo o hardware. A ABC71, por exemplo, passou bastante tempo usando BDE em suas aplicações para acessar banco de dados. Quando a Borland (hoje Embarcadero) descontinuou a evolução dessa biblioteca, tivemos que adotar outra solução para acesso aos dados.

O Design Pattern estrutural Adapter, às vezes também chamado de Wrapper, fornece uma solução bastante prática para esse tipo de problema, de modo que foi preciso alterar basicamente apenas um arquivo fonte para por nosso ERP Omega para funcionar com ADO no lugar do BDE. Veja abaixo o diagrama UML para a solução típica do Adapter.

Diagrama UML para Adapter
A solução típica é composta de 4 classes, sendo que a nomenclatura delas é a seguinte:
O Target é a classe que define a interface que o programa vai enxergar, isso é, quais são os nomes de métodos e propriedades que estarão disponíveis para o programa, independente de qual tecnologia será realmente utilizada. No diagrama anterior, a classe TWDBManager exerce esse papel.
O Adapter faz o meio de campo, isto é, implementa os métodos descritos pelo Target usando métodos e propriedades expostos pela classe que está sendo encapsulada (o Adaptee). No diagrama acima, esse papel cabe às classes TWBDEMan e TWADOMan, que adaptam o acesso respectivamente às funções do BDE e do ADO.
O Adaptee corresponde à classe que efetivamente realiza as tarefas que precisamos. Ela pode vir tanto de uma tecnologia específica dentro do mesmo ambiente de desenvolvimento (como é o caso do ADO e do BDE) quanto vir de uma biblioteca ou componente de terceiros.
A classe Client faz uso dos métodos expostos pelo Target para realizar as operações de que precisa. Ela não tem conhecimento de qual tecnologia ou biblioteca ou componente está sendo de fato usado. No exemplo, esse é o papel exercido pelo TWBusinessObj.

Qual das classes que implementam a interface do Target deve ser instanciada é uma decisão que pode ser resolvida pelo Pattern Factory Method, já que todas as soluções possíveis estão incluídas no programa e são conhecidas.

Em Delphi, a implementação do Adapter é resolvida através de herança onde a classe base (o Target) é abstrata:
type
TWDBManager = class
{ ... }
procedure Connect;virtual;abstract;
procedure BeginTrans;virtual;abstract;
procedure Commit;virtual;abstract;
procedure ExecSQL;virtual;abstract;
procedure OpenSQL;virtual;abstract;
end;

TWBDEMan = class(TWDBManager)
{ ... }
procedure Connect;override;
procedure BeginTrans;override;
procedure Commit;override;
procedure ExecSQL;override;
procedure OpenSQL;override;
end;

TWADOMan = class(TWDBManager)
{ ... }
procedure Connect;override;
procedure BeginTrans;override;
procedure Commit;override;
procedure ExecSQL;override;
procedure OpenSQL;override;
end;

O que a classe TWBusinessObj precisa conhecer é apenas a interface - todos os métodos necessários já estão definidos nela:
type
TWBusinessObj = class
{ ... }
_DBMan : TWDBManager;
procedure ExecTransaction;
end;

{ ... }

procedure TWBusinessObj.ExecTransaction;
begin
_DBMan.BeginTrans;
{ outras operações aqui }
_DBMan.Commit;
end;

Aqui, usei o padrão Adapter para substituir completamente uma tecnologia por outra que estava sendo descontinuada mas não é preciso que o cenário seja tão radical para poder aplicar esse padrão. Pode ser usado também em situações em que o usuário pode optar por uma tecnologia entre várias compatíveis. Por exemplo, numa aplicação de jogo pode-se optar por usar emulação de operações de vídeo ou submetê-las diretamente à placa de vídeo.

Mais Informações
Posts sobre Design Patterns

7 de setembro de 2009

Cronologia dos 40 anos da internet

Na semana passada, a internet se tornou uma quarentona. No dia 2 de setembro de 1969 foi realizado com sucesso a primeira troca de dados entre dois computadores numa rede militar experimental - a Arpanet. Embora os dados trocados não tivessem qualquer sentido, a tecnologia desenvolvida deu o pontapé inicial que culminou na atual rede mundial de computadores, com todas as suas consequências. Não vou fazer aqui um tratado sociológico do impacto da internet; apenas reproduzirei a cronologia dos principais eventos envolvendo-a. O texto original foi produzido pela Associated Press e segue conforme foi publicado na Folha online:
1969: Em 2 de setembro, dois computadores na UCLA (Universidade da Califórnia, Los Angeles) trocam dados sem sentido no primeiro teste da Arpanet, uma rede militar experimental. A primeira conexão entre dois locais --a UCLA e a Stanford Research Institute, também na Califórnia-- acontece em 29 de outubro, apesar de a rede ser interrompida após digitarem as duas primeiras letras da palavra "logon". A Universidade da Califórnia Santa Bárbara e a Universidade de Utah também se juntam à rede depois.

1970: A Arpanet chega à sua primeira ligação na costa leste dos Estados Unidos, na empresa Bolt, Beranek e Newman --agora BBN Technologies--, em Cambridge, Massachusetts.

1972: Ray Tomlinson traz também o e-mail à rede, escolhendo o símbolo "at" ou "@" como maneira de especificar endereços de e-mail pertencendo a outros sistemas.

1973: A Arpanet ganha suas primeiras ligações internacionais, na Inglaterra e Noruega.

1974: Vint Cerf e Bob Kahn desenvolvem a técnica de comunicações TCP, permitindo que múltiplas redes se compreendam, criando a verdadeira internet. Posteriormente, o conceito se divide em TCP/IP antes de sua adoção formal, em 1º de janeiro de 1983.

1983: O DNS (Domain Name System) é proposto. A criação de sufixos como ".com", ".gov" e ".edu" chega um ano depois.

1988: Um dos primeiros worms da internet, Morris, causa danos a milhares de computadores.

1989: A Quantum Computer Services, agora AOL, inaugura o serviço America Online para computadores Macintosh e Apple 2, começando uma expansão que acabaria por conectar cerca de 27 milhões de norte-americanos em 2002.

1990: Tim Berners-Lee cria a WWW (World Wide Web) enquanto desenvolvia maneiras de controlar computadores a distância na Cern (Organização Europeia para Pesquisa Nuclear).

1993: Marc Andreessen e colegas na Universidade de Illinois criam o Mosaic, primeiro navegador a combinar gráficos e texto em uma única página, abrindo a web para o mundo com um software fácil de usar.

1994: Andreessen e outros na equipe do Mosaic formam uma empresa para desenvolver o primeiro navegador comercial, o Netscape. Isso chama a atenção da Microsoft e de outros desenvolvedores que iriam investir no potencial comercial da web. Dois advogados da área de imigração apresentam o spam ao mundo, ao fazer propaganda de seus serviços de "green card lottery" --programa de distribuição de vistos norte-americanos.

1995: A Amazon.com abre suas portas virtuais.

1998: Google monta um projeto iniciado nos dormitórios de Stanford. O governo dos Estados Unidos delega a supervisão das políticas relacionadas a nomes de domínios para a Icann (Internet Corporation for Assigned Names and Numbers). O Departamento de Justiça e 20 Estados acusam a Microsoft, criadora do onipresente sistema operacional Windows de abusar de seu poder de mercado, frustrando a competição com o Netscape e outros.

1999: O Napster populariza o compartilhamento de arquivos de música, levando a sucessores que mudaram permanentemente a indústria das gravadoras. A população usuária de internet no mundo ultrapassa 250 milhões de pessoas.

2000: O "boom" das empresas de tecnologia dos anos 1990 dá lugar à explosão da bolha do setor. A Amazon.com, eBay e outros sites são seriamente prejudicados em um dos primeiros usos em larga escala do ataque de negação de serviço, que enche um site com tanto tráfico falso que usuários de verdade não conseguem visitá-lo.

2002: A população usuária de internet do mundo ultrapassa 500 milhões de pessoas.

2004: Marck Zuckerberg inicia o Facebook, em seu segundo ano de curso na Universidade Harvard.

2005: É inaugurado o site de compartilhamento de vídeos YouTube.

2006: A população usuária de internet do mundo ultrapassa 1 bilhão de pessoas.

2007: A Apple lança o iPhone, trazendo o acesso a internet sem fio a mais milhões de pessoas.

2008: Os usuários de internet do mundo ultrapassam 1,5 bilhões de pessoas. O total só na China chega a 250 milhões, ultrapassando os Estados Unidos como o país com a maior população usuária de internet do mundo. Os desenvolvedores do Netscape interrompem o navegador pioneiro, embora seu "sucessor", Firefox, permaneça forte. Importantes companhias aéreas intensificam o uso de serviços de internet nos voos.

2009: O "Seattle Post-Intelligencer" torna-se o primeiro grande jornal diário a ficar exclusivamente online. O Google anuncia o desenvolvimento de um sistema operacional com foco na web.


4 de setembro de 2009

Trabalhando com Threads em Delphi - Seções Críticas

Um dos aspectos mais importantes com que se preocupar quando desenvolvemos software envolvendo threads é o acesso a recursos compartilhados. Isto é, a forma com que vamos lidar com variáveis globais em memória, gravação e leitura de arquivos, impressão, acesso à interface visual, etc., de modo que seja garantido que todas as threads sempre encontrem esses recursos num estado apropriado para uso.

Pense, por exemplo, que você criou uma thread para imprimir relatórios em background para seu sistema. Você disponibiliza uma lista global onde o usuário pode ir adicionando novos relatórios a serem impressos. Sua thread de impressão busca nessa mesma lista qual é o próximo relatório a imprimir.

Antes de prosseguir, lembro alguns detalhes para tornar mais claro o contexto. Quando você escreve um programa em Delphi ou outra linguagem de alto nível, o compilador converte cada linha do seu programa em diversas instruções de baixo nível que podem ser executadas pelo computador. Toda thread é, então, composta por uma sequência das instruções originadas a partir das várias linhas do código que você escreveu. O Sistema Operacional executa as threads submetendo a sequência de instruções de cada uma delas por um tempo determinado (chamado time slice). Como esse tempo é determinado pelo Sistema Operacional, não há garantias de que todo o bloco gerado a partir da linha de código Delphi será executado todo de uma vez.
Operação de threads
Na figura acima, se o tempo destinado à Thread Principal se esgotar no ponto desenhado, o estado das variáveis de programa envolvidas pode não ter sido completamente atualizado. Outra Thread que acessar uma variável nessa condição encontrará lixo.

Voltando ao exemplo da thread de impressão, é preciso garantir que o código que manipula a lista seja executado como uma unidade. Sistemas Operacionais como o Windows disponibilizam um mecanismo denominado Seção Crítica para resolver esta questão. No Delphi e C++ Builder, a classe TCriticalSection (que está na unit syncobjs) encapsula o funcionamento desse mecanismo. A ideia é simples : cria-se uma instância de seção crítica para cada recurso que se queira proteger, proteção essa que se dá envolvendo o acesso ao recurso com a chamada a 2 funções - uma para entrar na seção crítica e a outra para liberar o acesso ao recurso novamente. Ao adicionar um novo Job à lista de impressão:
try
_scPrintJobs.Enter;
_Jobs.Add (pNovoJob);
finally
_scPrintJobs.Leave;
end;
E também quando a Thread responsável pela impressão for remover um job para executá-lo:
try
_scPrintJobs.Enter;
lJob := _Jobs.Items[pJobIdx];
_Jobs.Delete (pJobIdx);
finally
_scPrintJobs.Leave;
end;
É conveniente utilizar tratamento de exceções (par try/finally) pois a seção crítica é um recurso bastante sensível. Se ocorrer algum problema e o método Leave nunca for executado, o seu programa fatalmente vai deixar de funcionar. É bom usar com critério o quê vai ser colocado dentro da seção crítica pois um processamento pesado pode fazer com que o restante da aplicação pare de responder.

Por questão de organização, a criação da seção crítica pode ser feita na própria unit reservada ao recurso que ela deve proteger:
var _scPrintJobs : TCriticalSection;
{...}
initialization
_scPrintJobs := TCriticalSection.Create;

finalization
_scPrintJobs.Destroy;
end.
Seções Críticas devem ser utilizadas somente dentro de um mesma aplicação. Se pretende construir uma biblioteca (DLL) que poderá ser utilizada por mais de uma aplicação ao mesmo tempo, o mecanismo mais apropriado é o Mutex (acesso mutuamente exclusivo). Os Mutexes funcionam do mesmo modo que as seções críticas e, embora possam ser usados também quando o escopo é o mesmo processo, são ligeiramente mais lentos.

2 de setembro de 2009

Usando interfaces em Delphi - parte 3

Vou montar neste post um exemplo do uso de interfaces em Delphi, mostrando as formas que encontrei para evitar as armadilhas apontadas no post anterior. A ideia é modelar uma classe que seja capaz de ordernar uma lista sem se preocupar com qual é de fato o tipo do objeto que está sendo classificado - basta que ele implemente uma interface apropriada, que chamarei aqui de IWSortable.

O primeiro ponto é a necessidade de referenciar o objeto original que foi atribuído à interface - lembre-se que ponteiro para interface é incompatível com ponteiro para class. No caso específico do IWSortable, a função de comparação poderá fazer uso de qualquer das propriedades de uma classe de objetos para determinar a ordem correta de cada um dentro da lista. Se for trabalhar, por exemplo, com uma lista de Pessoas, a ordem poderá ser primeiro por nome, depois por idade e envolver quantos campos mais forem necessários.

Minha solução para isso é criar uma interface base com uma função que retorne o ponteiro para o objeto atual. Todas as minhas interfaces, então, herdarão dessa base:
IWBaseInterface = interface
['{97DF568E-D4ED-4F1A-99DF-07E524BD598D}']
function GetSelfObj : TObject;
end;

IWSortable = interface(IWBaseInterface)
['{7CEF3AE4-9127-460E-B1A1-8D18B7CA7D05}']
function CompareTo (AObj : IWSortable) : integer;
end;
Com isso, todas as classes que implementarem a interface serão capazes de reportar seu próprio ponteiro quando solicitadas. Para facilitar, também criei uma classe base para fornecer essa funcionalidade como padrão.
TWInterfacedObject = class(TObject, IWBaseInterface)
{...}
public
function GetSelfObj : TObject;
end;
{...}
function TWInterfacedObject.GetSelfObj : TObject;
begin
Result := Self;
end;
A função precisa apenas reportar o ponteiro Self para satisfazer a interface IWBaseInterface. Uma classe hipotética TWPessoa poderia implementar o método CompareTo recorrendo a esta função :
TWPessoa = class(TWInterfacedObject, IWSortable)
public
idade : integer;
nome : String;
function CompareTo (AObj : IWSortable) : integer;
end;
{...}
function TWPessoa.CompareTo (AObj : IWSortable) : integer;
var pessoa : TWPessoa;
begin
Result := 0;
pessoa := AObj.GetSelfObj As TWPessoa;

if (nome < pessoa.nome) then
result := -1;
if (nome > pessoa.nome) then
result := 1;

// se os nomes são iguais, ordena pela idade
if (Result = 0) then
Result := idade - pessoa.idade;
end;
Veja que TWPessoa herda de TWInterfacedObject - que fornece a função GetSelfObj - e também implementa a interface IWSortable, o quê exige a presença da função CompareTo para comparar 2 objetos. Veja também que esta função faz a comparação usando mais de um campo da classe pessoa - justamente porque consegue obter o ponteiro para a Pessoa original a partir da interface informada no parâmetro AObj.

Felizmente, a versão 2010 do Delphi lançada recentemente pela Embarcadero resolve nativamente esse problema. Nesta versão, ponteiros para interfaces passam a ser compatíveis com ponteiros para classes. Veja o post de Malcon Groves sobre o assunto aqui. Para versões antigas, a interface base ainda terá que ser usada.

O segundo questão a resolver é o fato das interfaces Delphi terem suas referências contadas (veja o post anterior). Depois que um objeto é atribuído a uma variável do tipo interface, o controle do ciclo de vida do objeto passa a ser feito automaticamente, significando que ele pode ser destruído antes do que você imagina. Como todo o resto no Delphi para Win32 tem ciclo controlado pelo programador, acredito que misturar os dois mecanismos traz mais confusão do que benefícios. Além de ser mais sujeito a erros.

Aproveitei então a classe base TWInterfacedObject e implementei nela os requisitos exigidos pelo tratamento de interfaces do Delphi. Isto é, escrevi minha versão para as funções do IUnknown de forma que que a contagem de referências é sempre 1, me deixando livre para destruir os objetos no ponto que eu julgar mais conveniente. A classe ficou como segue:
TWInterfacedObject = class(TObject, IWBaseInterface)
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
function GetSelfObj : TObject;
end;
{...}
function TWInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;

function TWInterfacedObject._AddRef: Integer;
begin
Result := 1;
end;

function TWInterfacedObject._Release: Integer;
begin
Result := 1;
end;
Essas soluções apresentadas aqui funcionam bem se você pretende usar interfaces sem qualquer vínculo com a tecnologia COM. Caso contrário, é preferível utilizar os mecanismos padrões do Delphi pois eles foram construídos justamente para fornecer essa interação.

Mais Informações
Usando interfaces em Delphi - parte 1 e parte 2.

1 de setembro de 2009

Usando interfaces em Delphi - parte 2

Há alguma confusão quando se fala em interface em programação porque o termo remete à tecnologia COM da Microsoft e se mistura com ele. Colabora para a confusão o fato de que a interface base em Delphi tem o nome de IUnknown, o mesmo de uma interface amplamente utilizado no COM e que tem o mesmo objetivo, que é controlar a quantidade de referências a um objeto e também permitir que se descubra se esse objeto implementa ou não uma interface arbitrária. Na verdade, o projeto do COM é centrado no conceito de interfaces e, portanto, não poderia ter sido implementado no formato atual sem elas. E, quando o recurso de interfaces no Delphi foi desenhado, tinham como alvo a estrutura necessária para interagir com COM de modo que trabalhar com essa tecnologia fosse algo trivial.

Apesar de ter uma ligação tão intrincada com o COM, mostrei no post anterior que o conceito de interfaces em Delphi pode ser usado de forma desvinculada. Mesmo assim, a escolha de introduzir interfaces tendo o COM em mente produziu alguns efeitos colaterais que não podem ser negligenciados durante o projeto e programação de sistemas onde se queira usar esse recurso.

Em primeiro lugar, as interfaces em Delphi tem suas referências controladas através da implementação do IUnknown. Sempre que você atribui o ponteiro de um objeto ou de uma interface a uma variável de interface, o contador de referências é incrementado. Quando a variável de interface sai de escopo, o contador é automaticamente decrementado e quando chega a zero, o objeto original é removido da memória. Isso foge da forma tradicional de se controlar o ciclo de vida de um objeto em programas Win32 já que a implementação do IUnknown funciona como um garbage collector. Isto é, você cria o objeto que implementa uma interface mas é o compilador quem decide o momento em que o destructor será invocado.

No entanto, se o ponteiro do objeto que você criou jamais for atribuído a uma variável do tipo interface, você ainda é responsável por destruir esse objeto! É fácil ver a bagunça que vai virar se o planejamento não for bem feito, com memória sendo perdida ou erros de access violation sendo gerados do nada. Quando usado em conjunto com o COM, entretanto, a estrutura de criação através de CoClasses se encarregará de destruir corretamente o objeto.

Formas para resolver esse impasse podem ser encontradas num artigo publicado no EDN (Embarcadero Developer Network) : Delphi reference counted interfaces.

O segundo ponto com o qual se preocupar é com a necessidade de recuperar um ponteiro para a classe original a partir de um ponteiro da interface. Isto é, você instanciou a classe, atribuiu o ponteiro a uma interface e agora quer recuperar de novo o ponteiro para a classe. No Delphi, variáveis do tipo class são diferentes de variáveis do tipo interface e elas não são intercambiáveis como em outras linguagens de programação menos ligadas ao COM. De fato, uma variável para classe contem o endereço de memória onde estão os dados dessa classe enquanto que a interface é apenas um ponteiro para a tabela virtual de métodos dessa interface. São, portanto, incompatíveis e isso deve fazer parte do planejamento da hierarquia de classes e interfaces.

Por fim, as interfaces do Delphi funcionam melhor quando são vinculadas a um GUID pois a base para verificar se uma classe implementa uma determinada interface é a função QueryInterface introduzida pelo IUnknown e usada à exaustão quando se trabalha com interfaces. E essa função pede uma identificação única na forma de um GUID para a interface que se deseja pesquisar ou instanciar. Você pode pressionar as teclas <Ctrl-Shift-G> no IDE do Delphi para ter um novo GUID gerado. Ao declarar uma interface, inclua o GUID como no exemplo:
IWSortable = interface
['{7CEF3AE4-9127-460E-B1A1-8D18B7CA7D05}']
function CompareTo (Elemen: IWSortable): Integer;
end;

Volto em outra oportunidade com esse assunto para mostrar um implementação funcional de interface de sort.