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