28 de setembro de 2010

Design Patterns com Delphi - Iterator - Parte II

Tomando como o base a situação descrita no post anterior - sobre o Design Pattern comportamental Iterator - eu mostro aqui como estruturar classes em Delphi para implementar aquele exemplo.

Apenas para manter a referência, reproduzo abaixo novamente o Diagrama de Classes usado naquele post que representa a solução proposta.
Diagrama UML para o padrão Iterator

O ponto focal da solução é a classe tida como Client, isto é, aquela que fará uso do Iterator, pois ela determina algumas características básicas que afetarão o desenho do sistema como um todo. Por exemplo, espera-se que a classe TWBrowseForm do diagrama acima seja capaz de recuperar estruturas de cada lista e alimentar com elas um Grid na tela - talvez até mesmo definindo novas classes com adoção de um padrão MVC (Model-View-Controller). Veja no quadro abaixo uma proposta para a classe TWBrowseForm.
TWBrowseForm = class (TForm)
_Grid : TStringGrid;
{ ... }
protected
_Dados: TWCollection;

public { ... }
procedure SetCollection (ADados: TWCollection);
procedure ExibirRegistros;
end;

{ ... }
procedure TWBrowseForm.SetCollection (ADados: TWCollection);
begin
_Dados := ADados;
end;

procedure TWBrowseForm.ExibirRegistros;
var lIterator : TWIterator;
lBObj : TWBusinessObj;
begin
{ ... }
lIterator := _Dados.CreateIteratorObj;
lIterator.Primeiro;

while (not lIterator.FimColl) do
begin
lBObj := lIterator.CurReg;
lBObj.ExibeNoGrid(_Grid);

lIterator.Proximo;
end;
{ ... }
end;

A classe é uma herança de um Form na qual foi incluido um Grid para exibir os registros recuperados. Os registros que serão exibidos, por sua vez, estão armazenados num coleção nomeada como _Dados e cujo tipo é a classe genérica TWCollection. Tudo o que o nosso Form precisa saber é que essa coleção consegue construir um iterator para nos permitir navegar pelos registros nela contidos. Por isso, a classe de coleção é definida como abstrata, forçando todas as coleções reais a disponibilizarem sua própria versão de Iterator.
TWCollection=class
public
function CreateIteratorObj:TWIterator;virtual;abstract;
end;

TWColCarrinhoCompras=class(TWCollection)
protected
_Lista : TList;
public
procedure AddItem (AItem: TWBusinessObj);
function CreateIteratorObj:TWIterator;override;
end;


TWColMovtosEstoque=class(TWCollection)
protected
_Query: TAdoQuery;
public
function CreateIteratorObj:TWIterator;override;
end;

Note que cada coleção real neste exemplo está preparada para usar uma forma diferente de organização das informações - uma usará diretamente uma lista interna enquanto a outra deverá ler as informações no banco de dados em algum momento. Veja também que o método que cria o iterator não é abstrato nelas - apenas na base. Isto significa que elas terão que providenciar codifição para esse método, cada uma respeitando sua própria organização interna.

A construção dos iterators também segue uma regra bem definida, estabelecida através de uma classe base abstrata que introduz os métodos para navegação. Esses métodos deverão ser obrigatoriamente implementados pelos iterators reais, em associação com as respectivas coleções.
TWIterator=class
public
procedure Primeiro;virtual;abstract;
procedure Proximo;virtual;abstract;
function FimColl : Boolean;virtual;abstract;
function CurReg : TWBusinessObj;virtual;abstract;
end;

TWItCarrinho=class(TWIterator)
protected
_CurIdx : Integer;
_Col: TWColCarrinhoCompras;
public
Constructor Create (ACol: TWColCarrinhoCompras);

procedure Primeiro;override;
procedure Proximo;override;
function FimColl : Boolean;override;
function CurReg : TWBusinessObj;override;
end;

TWItMovtosPorData=class(TWIterator)
protected
_Col: TWColMovtosEstoque;
public
Constructor Create (ACol: TWColMovtosEstoque);

procedure Primeiro;override;
procedure Proximo;override;
function FimColl : Boolean;override;
function CurReg : TWBusinessObj;override;
end;

A classe TWBusinessObj que aparece no código acima é o tipo de dado que esse Iterator em particular pode manipular e utilizá-la é uma decisão tomada durante a fase de projeto. Dependendo do uso que se pretente, poderíamos ter optado pela classe mais básica do Delphi - o TObject -, propiciando que qualquer classe seja utilizada na solução. O TWBusinessObj não foi incluído no diagrama UML do Iterator porque não é parte da solução do Pattern em si. Ele aqui representa objetos de negócio que têm a capacidade de se desenhar como uma linha num grid. O quadro acima também reforça a ideia de que os iterators reais estão intimamente conectados à coleção na qual eles estão associados. Isso ocorre porque eles precisam conhecer a forma com que a coleção está estruturada para poder extrair-lhe os registros. Veja a implementação para o carrinho de compras:
procedure TWItCarrinho.Primeiro;
begin
_CurIdx := 0;
end;

procedure TWItCarrinho.Proximo;
begin
Inc (_CurIdx);
end;

function TWItCarrinho.FimColl : Boolean;
begin
Result := (_CurIdx >= _Col.GetLista.Count);
end;

function TWItCarrinho.CurReg : TWBusinessObj;
begin
if (_CurIdx >= 0) And (_CurIdx < _Col.GetLista.Count) then
Result := TWBusinessObj(_Col.GetLista.Items[_CurIdx])
else
Result := Nil;
end;

Para ligar isso tudo, é preciso um contexto onde se tenha uma coleção que deva ser exibida numa tela com grid - ainda que isso não faça parte do design pattern em si.
var lForm : TWBrowseForm;
begin
lForm := TWBrowseForm.Create (Self);
lForm.SetCollection (_Colecao);
lForm.ExibirRegistros;
{ ... }

No exemplo acima, a variável _Colecao representa uma coleção qualquer instanciada em outro ponto, conforme as necessidades específicas do sistema. Ela também teve a oportunidade de ser alimentada diretamente com dados (no caso do carrinho de compras) ou de ter seus filtros estabelecidos (para as queries nas coleções de movimentos de estoque ou de cadastro de clientes).

17 de setembro de 2010

Design Patterns com Delphi - Iterator - Parte I

Trabalhar com estruturas de dados organizadas em coleções como listas, mapas e filas, entre outras, é situação extremamente comum para quem programa computadores. De tão corriqueira, muitas linguagens de programação incorporaram em suas bibliotecas mecanismos genéricos para proporcionar a navegação pelos elementos adicionados a uma coleção. É o caso da plataforma .NET e do Java - ambos possuem interfaces com esse objetivo que têm o nome de Iterator – e também do C++, onde esse recurso é uma das funcionalidades incluídas como um template da STL.

O tipo de navegação descrito acima também é abordado por um dos Design Patterns comportamentais mais utilizados, chamado justamente de Iterator. A ideia dele é permitir que se percorra os elementos de uma coleção sem que seja necessário conhecimento prévio do tipo de estrutura de dados que está armazenada na coleção nem o método de organização da coleção. Esta abstração, portanto, dá ao programador a possibilidade de criar uma infraestrutura genérica para tratar certas situações num sistema. Uma vez pronta e testada a infraestrutura, agregar novas funcionalidades a ela passaria a ser tarefa bem mais simples, implementando-se apenas novas classes periféricas.

Como exemplo de uma infraestrutura assim, imagine a construção de uma tela genérica que é capaz de exibir linhas numa tabela. Cada linha desenhada na tabela corresponde a um registro retornado por uma coleção qualquer, sendo que a origem dos dados dessa coleção pode ser uma consulta ao banco de dados, uma lista de registros selecionados na mão pelo usuário, etc. A ordem com que os registros devem ser listados é inerente à própria coleção, livrando a tela de ter que conhecer de antemão essa ordem. Uma representação dessa proposta usando o pattern Iterator é retratada no quadro abaixo.
Diagrama UML para o padrão Iterator

Formalmente, a nomenclatura para as classes integrantes dessa solução é a seguinte.
O Iterator é uma classe abstrata que estabelece o formato das funções para percorrer os itens da coleção, permitindo acesso aos itens individuais armazenados numa coleção, independentemente do tipo de dado real. Essa classe, portanto, fornece uma abstração para o acesso ordenado aos dados contidos na coleção. No diagrama de exemplo acima, esse é o papel da classe TWIterator.
São classificados como ConcreteIterator as classes que implementam as funções estabelecidas pelo Iterator , providenciando os mecanismos reais de acesso típico de cada estrutura de dados - listas, pilhas, filas, recordsets do banco de dados, etc. Como cada ConcreteIterator conhece a forma com que os dados são organizados numa coleção, eles servem de ponte para que outras classes possam ter acesso aos dados através de uma interface comum (representada pelo próprio Iterator). Há 3 classes desse tipo no diagrama de exemplo: TWItMovtosPorData, TWItClientes e TWItCarrinho.
O Aggregate é uma classe abstrata que define a interface para a criação do Iterator associado a uma coleção. Com isso, partes do programa que utilizem coleções podem instanciar corretamente o Iterator necessário para percorrer os itens da coleção, sem precisar se preocupar com a estrutura da coleção nem com o tipo de dado armazenado. No nosso exemplo, a interface é definida pela classe TWCollection.
Os ConcreteAggregate são classes que providenciam a criação do ConcreteIterator apropriado à coleção que cada um deles representa. Veja, portanto, que cada ConcreteIterator está intimamente ligado a ao menos um ConcreteAggregate, conhecendo-lhe sua estrutura e organização. Por isso, nosso exemplo também tem 3 classes do tipo ConcreteAggregate : TWColMovtosEstoque, TWColClientes e TWColCarrinhoCompras.
Por fim, qualquer classe que consuma uma coleção (Aggregate) e um Iterator são tidas como Clients. É esse o caso da classe TWBrowseForm incluída no diagrama do exemplo.
No próximo post eu mostro como codificar as classes envolvidas no exemplo apresentado aqui.
Mais Informações
Posts sobre Design Patterns

13 de setembro de 2010

Sobre a chamada de funções virtuais no construtor de uma classe

O desenvolvimento de software feito na ABC71 é um processo contínuo pois nosso ERP é o mesmo produto em todos os nossos Clientes. Quero dizer com isso que novas manutenções nunca partem do zero, o que faz com que tenhamos que nos preocupar também com códigos fontes legados. Como é parte do meu trabalho pesquisar para fazer avançar o sistema e ao mesmo tempo garantir que permanece funcionando o que já existe, falo neste post sobre uma situação na qual esse embate esteve presente.

Imagine que você projetou uma classe base com algumas funções virtuais e heranças dessa classe que se beneficiam de tais funções. Com o passar do tempo, estas implementações se consolidam como parte do sistema, encorporando-se ao código legado. Surge, então, a necessidade de criar uma nova herança daquela classe base. Por causa dessa nova classe, percebe-se que parte do código existente no construtor da classe base precisa ser específico em cada herança já que ele prepara algumas estruturas que, embora estejam declaradas na classe base, apenas as heranças têm conhecimento de como fazer a preparação. No meu caso concreto, a classe base introduz tratamentos genéricos envolvendo acesso a banco de dados enquanto as heranças tratam questões específicas para bancos de dados distintos.

O primeiro impulso é o de criar uma nova função virtual na classe base e implementá-la nas heranças conforme a necessidade de cada uma delas. O problema é que a chamada teria que ser feita no construtor da classe base para que a alteração surtisse o efeito desejado ! E, nesse ponto, o que está em construção é a própria classe base, não existindo ainda uma instância da classe herdada, a qual estamos de fato tentando construir !

A figura abaixo ilustra a sequência de chamadas que é gerada pelo compilador para o código referente à construção da instância de uma classe:
Sequência de execução de funções na construção de instâncias

A imagem mostra duas classes com relação de herança entre si, TWClasseBase e TWHerancaA. O código do construtor da classe base chama uma função virtual - Initialize - que é reescrita na classe herdada.

Sempre que criamos a instância de uma classe, o processo de construção se dá de cima pra baixo. Isto é, primeiro é criada a instância da classe mais básica, seguindo sequencialmente a hierarquia até a chamada do construtor da classe que estamos realmente instanciando. Isto ocorre porque você pode usar em sua classe - mesmo no constructor - qualquer estrutura que tenha sido introduzida nas classes superiores, gerando a necessidade de que estas estruturas já estejam iniciadas quando o construtor da sua herança for chamado.

Observe na figura que no momento em que a função virtual Initialize precisa ser chamada, apenas a classe base existe. Portanto, a tabela de funções virtuais só tem a referência ao Initialize da classe base, ignorando completamente aquele que foi escrito para a classe herdada.

Voltando ao caso original... Para resolver em definitivo o problema, uma opção seria reprojetar as classes, contornando a limitação. No entanto, eu teria como efeito colateral a obrigação de modificar literalmente centenas de fontes.

Encontrei a solução numa classe especial, chamada TObject. Em Delphi, esta classe especial serve automaticamente de base para todas as demais classes. Em C++ Builder, esta classe também existe mas você tem que declarar explicitamente a herança para poder se beneficiar dos recursos dela. O recurso que usei é uma função virtual chamada AfterConstruction introduzida pelo TObject. A própria linguagem garante que esta função será chamada automaticamente logo após a execução de todos os construtores necessários. Então, passei a chamada da minha função virtual Initialize para dentro do AfterConstruction da minha classe base. Agora, o Initialize chamado é o correto, associado à classe que realmente é instanciada.
char __fastcall TWClasseBase::AfterConstruction (void)
{
/* equivale ao inherited do Delphi */
TObject::AfterConstruction();

Initilize();
}


Mais Informações
Classe TObject

7 de setembro de 2010

Os benefícios intangíveis dos cursos de MBA

por Bianca Machado Branco*

A proliferação dos cursos de MBA (Master of Business Administration) é um fato. Porém, o que ocorre, ultimamente, é que o público dessa modalidade de pós-graduação também está se modificando, abrangendo não apenas a alta gerência como também profissionais do nível operacional.

Cada vez mais os cursos de MBA vêm sendo procurados por jovens profissionais, muitos recém-formados, com pouco ou nenhum tempo de carreira, porém com expectativas muito altas em relação ao retorno em termos financeiros que o curso trará para eles. Esses profissionais são a chamada geração Y, aqueles nascidos na década de 80, que possuem uma característica marcante: o imediatismo. Eles iniciam um MBA pouco tempo depois de concluída a graduação e esperam com isso uma inserção imediata no mercado de trabalho ou um reconhecimento rápido das empresas onde atuam - como um aumento salarial significativo, uma promoção de cargo ou outra forma de compensação financeira.

Entretanto, a especialização por si só não oferece garantias de sucesso. A experiência de um MBA pode representar valor agregado sob o ponto de vista das empresas, mas o retorno que o profissional trará à empresa demanda um longo tempo para que se possa medir. A companhia não recompensará um título por si só, mas os resultados que o profissional trouxer após aplicar no seu dia-a-dia o know-how absorvido no curso.

Expectativas financeiras à parte, um MBA também trás benefícios intangíveis, como:
Incremento da bagagem profissional, ampliada por meio de conhecimentos de várias áreas de negócios. Essa bagagem é o que ajudará o profissional a falar a língua do cliente e a se inserir em projetos mais desafiantes;

Aprender a pensar "fora da caixa". O currículo do MBA tradicional ensina a interpretar balanços contábeis, planos de marketing e elaborar um planejamento estratégico. Essas são ferramentas úteis para um entendimento de como se desenvolvem as operações de um departamento ou de uma empresa. Quanto melhor a compreensão por parte do profissional sobre os movimentos da empresa, melhor o seu posicionamento estratégico dentro dela;

Visão de oportunidades de negócios, que pode fazer com que o profissional ajude a alavancar o negócio da empresa (e com isso se destacar), ou até mesmo empreender um negócio por conta própria;

Networking, permitindo a troca de experiências com profissionais de várias áreas, trazendo visibilidade ao profissional no mercado e podendo, até mesmo, gerar futuras indicações;

Valorização do profissional no mercado, o que significa manutenção da empregabilidade.

Os benefícios tangíveis, como crescimento na carreira, serão consequências da experiência prática profissional, que vale mais que qualquer título. O que o MBA fará pelo profissional é muní-lo de um ferramental diferenciado, para que ele possa encontrar soluções que agreguem valor ao negócio da empresa.

*Bianca Machado Branco possui MBA em Gestão Empresarial e formação em Análise de Sistemas. Trabalha com tecnologia da informação desde 1998. Atualmente é coordenadora de projetos na ABC71.

3 de setembro de 2010

Criando Serviços que permitam várias instalações num mesmo computador

Como parte de sua solução de ERP Omega, a ABC71 entrega a seus Clientes um Serviço do Windows para execução de processos de forma agendada. Isto é, o Cliente dita a frequência e o horário mais apropriado e o Serviço, que pode ser instalado em outro computador, se encarrega da execução na data e hora indicada.

A maioria de nossos Clientes precisa gerenciar apenas um banco de dados, mesmo quando há mais de uma planta envolvida já que cada uma controla seu próprio banco. Essa característica permitiu ao Serviço descrito no parágrafo anterior atender muito bem a necessidade de agendamento desses Clientes. A situação mudou quando em alguns Clientes se fez necessário monitorar de forma centralizada as execuções em mais de um banco simultaneamente, cada um deles representando uma filial distinta do mesmo Cliente.

Um solução óbvia é fazer com que o mesmo Serviço do Windows seja instalado mais de uma vez e, então, cada instalação cuida de um banco de dados diferente.

Implementar essa solução num programa feito em C++ Builder ou Delphi é relativamente simples, exigindo duas alterações na forma de se registrar o serviço. Uma vez que o Windows não aceita a existência simultânea de serviços com um mesmo nome interno, a primeira modificação é fazer com que cada instalação seja feita com um nome diferente. A segunda modificação no programa diz respeito a como informar ao Serviço qual é o banco de dados que ele deve usar. A resposta curta para isso é passar a informação como um parâmetro na linha de comando registrada para o Serviço.

A linha de comando nada mais é que o nome do programa (com o caminho e a extensão EXE) seguido de valores separados por espaços em branco. Tais valores são repassados para o programa quando ele é executado, de forma que é possível lê-los e utilizá-los conforme a necessidade. A linha de comando de um serviço pode ser alterada após sua instalação através da função ChangeServiceConfig da API do Windows. Então, nós a chamaremos no evento AfterInstall do TService:
void __fastcall TSchedService::ServiceAfterInstall(TService *Sender)
{
SC_HANDLE mh = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (mh != NULL)
{
{ Abre o serviço com o Nome calculado para distinguir instâncias que apontem para bancos de dados diferentes. }
SC_HANDLE sh = OpenService(mh, Name.c_str(), SC_MANAGER_ALL_ACCESS);
if (sh != NULL)
{
{ Incluir como parâmetro de execução do serviço o nome da configuração que deve ser usada, conforme definido pelo Configurador do Scheduler }
AnsiString lParam = "USE_ALIAS=\"" + _CfgName + "\"";
AnsiString lPath = System::ParamStr (0) + " " + lParam;

ChangeServiceConfig(sh, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
SERVICE_NO_CHANGE, lPath.c_str(), NULL, NULL,
NULL, NULL, NULL, NULL);

CloseServiceHandle (sh);
}
CloseServiceHandle (mh);
}
};

A expressão System::ParamStr (0) dá acesso ao caminho completo do executável para o Serviço, isto é, do programa que está atualmente em execução. Veja que foi criado um par de valores no formato NOME=VALOR, que é acrescentado ao executável. Durante a execução do serviço eu consigo obter essa informação e extrair o valor. Segue o código para realizar essa extração em C++ Builder - no Delphi, varia apenas a sintaxe:
AnsiString __fastcall TSchedService::ExtractCfgName (void)
{
bool lOk = false;
AnsiString lNomeParam ("USE_ALIAS="), ret;
int i = 1, lCompr = lNomeParam.Length();

{ Percorre os parâmetros da linha de comando para encontra o que nos interessa }
while ((i <= System::ParamCount()) && (lOk == false))
{
ret = System::ParamStr (i);

if (ret.SubString (1, lCompr) == lNomeParam)
{
lOk = true;
{ Extrai apenas o valor após o sinal de igual }
ret = ret.SubString (lCompr+1, ret.Length ());
}
i ++;
}

if (! lOk)
ret = "";
return (ret);
};

No meu caso, o valor extraído representa uma chave do Registry onde gravei com antecedência toda a configuração para acessar o banco de dados correto mas poderia representar um nome de arquivo ou uma tag num XML, por exemplo. Em última análise, eu poderia ter passado todas as informações necessárias para a conexão com essa técnica mas isso tornaria mais complicado dar manutenção no programa, exigindo atenção especial à passagem dos parâmetros e ao limite de tamanho da linha de comando (normalmente, 256 caracteres).

Serviços do Windows têm um nome interno usado pelo sistema operacional como identificador único e um nome externo usado como uma descrição a ser apresentada ao usuário. Tanto o C++ Builder quanto o Delphi usam como nome interno do Serviço o valor da propriedade Name do componente TService. Por isso, é preciso modificar esse valor antes do programa ter a oportunidade de registrar o Serviço ou realizar qualquer outra operação que dependa do nome. Um bom lugar é o próprio construtor do TService.

Uma implicação importante ao se usar esse método é que alterar o nome de um componente nos força a respeitar as regras de nomeação do ambiente. Ou seja, o nome não pode ser iniciado por um número, não pode apresentar espaços em branco nem caracteres que não existam na língua inglesa, como caracteres acentuados (agudo, circunflexo, til, trema, crase) ou o cê cidilha. Podemos usar o próprio nome da configuração obtido anteriormente para diferenciar cada instância instalada. Para que isso funcione, teremos que preparar o nome obtido para garantir sua validade, substituindo os caracateres que não forem permitidos. Veja um exemplo:
char __fastcall TWConfigSched::PrepChar (char AChar)
{
char lCh;
switch (Byte (AChar) )
{
case 192: case 193: case 194: case 195:
case 196: case 197:
lCh = 'A';break;
case 199:
lCh = 'C';break;
case 200: case 201: case 202: case 203:
lCh = 'E';break;
case 204: case 205: case 206: case 207:
lCh = 'I';break;
case 210: case 211: case 212: case 213:
case 214: case 216:
lCh = 'O';break;
case 217: case 218: case 219: case 220:
lCh = 'U';break;
}

{ Outros caracteres inválidos são substituídos pelo underscore }
if ( ! ( (lCh >= '0' && lCh <= '9') ||
(lCh >= 'a' && lCh <= 'z') ||
(lCh >= 'A' && lCh <= 'Z') ) )
{
lCh = '_';
}

return (lCh);
};

Como esta função faz a troca de um único caracter, é preciso percorrer todo o nome do Serviço e trocá-los um a um para compor o novo nome. O conjunto de substituições reproduzido acima não está completo - falta tratar caracteres minúsculos e aqueles usados por línguas como o espanhol e o alemão (N com til, por exemplo). No entanto, as letras que não se encaixarem na substituição direta são trocadas por um underscore.

Mais Informações API de Serviços do Windows, Criação de Serviços (parte 1 e parte 2)