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