Como escrevi no artigo "Por que Interfaces?", o que pode ser mais interessante que modificar a mesma rotina sem perder as características iniciais e poder dar a possibilidade dela se modificar mais a ponto de atender todos os casos que possam existir. Pois seria não estarem dentro do executável e só serem carregadas no momento que fosse usar. Para quem não leu o artigo que mencionei acima, a situação
e um calculo de compras, que um cliente efetuou durante o mês no estabelecimento,
onde criamos uma classe onde passamos as comprar e ela me retorna o total, mas
surge à necessidade de esta mesma classe também ter a possibilidade
de calcular as compras e as que foram feitas ate o dia 10 de cada mês, conceder
um desconto de 10%.
Bom, a coisa agora começa a ficar mais interessante, para isso vamos
usar uma coisa muito pouco conhecida que e a BPL. Sabemos que e uma DLL modificada
pela Borland para uso do Delphi, legal, mas o que ela tem de especial que uma
DLL não faz? Pois bem, alem do fato de não precisar declarar os
métodos que estão dentro do BPL você ainda tem um recurso
bem interessante, que são as seções "Inicialization"
e "Finalization" que são automaticamente chamadas quando damos
um "Loadpackage" nesse modulo ou um "Unloadpackage". Neste
ponto nos podemos enxergar a Instancia da nossa aplicação e com
um pouco de pensamento lógico podemos inserir a classe deste modulo dentro
do Aplicativo sem que seja necessário o Aplicativo se preocupar com o
processo.
Vamos criar uma organização como abaixo:
Aplicativo -> Nucleo.BPL <-+-> Modulo1.BPL
|-> Modulo2.BPL
+-> Modulo3.BPL
O que temos em comum em todo o processo e que o Nucleo.BPL e visto por todos
os módulos envolvidos no processo, e como esta dentro de um BPL esta
Unit não será compilada dentro do executável, mas sim será
carregada no momento que o executável for aberto, mas não ira
carregar os módulos, apenas o Nucleo, já os módulos carregaremos
mais adiante quando forem necessários.
Abaixo veremos a seção de tipos da Unit presente em Nucleo.BPL:
Type
ICalculo = Interface
procedure Somar(Valor: Real; Data: TDateTime);
function Apurado: Real;
end;
TCalculo = Class(TInterfacedObject, ICalculo)
public
procedure Somar(Valor: Real; Data: TDateTime); virtual; abstract;
function Apurado: Real; virtual; abstract;
end;
TFechamento = class
public
function SaldoCliente(Dataset: TDataset; Modulo: string): Real;
end;
Aqui temos a Interface dos Objetos o esqueleto da classe com os seus métodos
como Abstract, sendo assim não será preciso declará-los,
já que esta classe não será usada e só servira de
estrutura para o delphi saber com o que ele estará lidando e a mesma
será herdada pelos módulos e nestes então será implementada.
A Classe TFechamento é a unida coisa que a aplicação vai
realmente ver e ela que fará o carregamento dos módulos, veja
que ha. ate um parâmetro para o nome do modulo que ela vai operar.
function TFechamento.SaldoCliente(Dataset: TDataset; Modulo: string): Real;
var
Hnd: Cardinal;
begin
Result := 0;
Hnd := LoadPackage(Modulo);
try
if not Assigned(fCalculo) then
raise Exception.Create('Classe de calculo do saldo não foi carregada.');
with Dataset do
begin
first;
while not Eof do
begin
fCalculo.Somar(FieldByName('Valor').AsFloat, FieldByName('Data').asDateTime);
Next;
end;
Result := fCalculo.Apurado;
end;
finally
UnloadPackage(Hnd);
end;
end;
end;
Podemos ver aqui que o modulo e simplesmente carregado e foi colocada uma verificação para saber se este implementou a classe dentro de FCalculo. Feito isto, passamos a enviar os dados para a classe e no final pegar o valor apurado.
Nos módulos existem dois detalhes importantes, primeiramente no uses
da unit principal do modulo existe uma chamada para a Unit do Nucleo e para
que esta não seja compilada dentro da BPL precisamos dizer que este modulo
e dependente de um outro BPL, para isto na fonte do DPK na seção
"Required" iremos adicionar "Nucleo", com isto ele saberá
que não precisa compilar este código, ficando assim dependente
deste modulo, como explicado acima eles se enxergarão mutuamente.
requires
rtl,
dbrtl,
Nucleo;
Agora já dentro da Unit do modulo definiremos a classe:
type
TCalculoNormal = Class(TCalculo, ICalculo)
private
FTotal: Real;
public
constructor Create;
procedure Somar(Valor: Real; Data: TDateTime); override;
function Apurado: Real; override;
end;
Implementamos os métodos e em seguida adicionamos ao final da fonte
as diretivas que farão à mágica acontecer:
initialization
fCalculo := TCalculoAlterado.Create;
finalization
FreeAndNil(fCalculo);
Veja que como a "Unit2" esta presente no "uses" deste modulo nos conseguimos
ver a variável fCalculo e então criar a classe nela.
E no momento de descarregar o Modulo então e limpa a variável.
Vejamos agora o código que foi implementado no executável para
que este carregasse Nucleo.BPL:
uses Unit2;
procedure TForm1.Button1Click(Sender: TObject);
var
fFechamento: TFechamento;
begin
fFechamento := TFechamento.Create;
Label2.Caption := 'Saldo: '+FormatFloat('#0.00', fFechamento.SaldoCliente(ClientDataSet1, Edit1.Text));
FreeAndNil(fFechamento);
end;
Nele também na diretiva "uses" existe a Unit do Nucleo e neste caso também precisamos
dizer ao executável que este código será externo e que ele não será preciso compilar
ele dentro do executável, para isto, abrimos as opções do projeto e na Guia Packages
Assinalamos o Item "Build with runtime Package" e no campo colocamos apenas "Nucleo"
assim também criaremos uma dependência de Nucleo no executável, e como já dito,
todos envolvidos no processo verão Nucleo e este se tornara o centro de tudo.
Agora vendo o processo desenvolvido, fica mais claro o porquê do Nucleo.BPL e
como este faz o carregamento dos módulos e como fica transparente o processo,
pois não importa o quão grande e o modulo, este será descarregado ao final do
processo liberando memória e dando espaço para outros módulos serem carregados,
tornando sua aplicação rápida, esperta, pois pode facilmente alterar a maneira
com que as coisas são processadas e o mais lindo de tudo que sua aplicação pode
receber versões em partes, não precisando enviar todo o sistema para um cliente
no momento de implementar uma nova rotina ou corrigir algo para um fim especifico.
Já trabalhei em aplicativos genéricos onde um simples executável de poucos Kbytes
gerenciava todo um sistema configurável de módulos interligados e que podia de
maneira transparente rodar em partes e ser adquirido pelos clientes conforme for
preciso.
Arquivos
do Artigo
Um grande abraço a todos e até o próximo artigo.
Marcelo Welter
Coordenador do Projeto Sultan
http://www.welter.pro.br/sultan
|