Orientação a Objetos no Delphi - Controle de Estoque (III)
1. Orientação a Objetos no Delphi: Controle de Estoque – Parte Final
Ryan Bruno C Padilha
ryan.padilha@gmail.com
http://ryanpadilha.com.br
Objetivo deste artigo
Este artigo é a parte final de uma série de três artigos onde abordamos o paradigma orientado
a objetos. No segundo artigo iniciamos a construção de uma aplicação de controle de estoque
básico, provando na prática que a OO não possui estruturas complexas de dados; ao contrário,
contempla um modelo simplificado de um conjunto de classes que se relacionam entre si, seja
através de heranças ou associações de agregação/composição. Nesta terceira parte será
revisto o modelo de domínio do controle de estoque, reforçando a utilização da notação UML,
que oferece uma documentação padronizada e simplificada do projeto de software. A
implementação em Object Pascal do restante das classes de domínio é realizado com base na
documentação referida acima, porém o objetivo agora é definir o comportamento dos objetos
de negócio instanciados na memória principal sobre a camada de visualização de dados
(view/formulário), exibindo a forma como operam com os campos do formulário e como
podem ser invocados por outro formulário.
1. Revisão do Modelo Conceitual
O modelo de domínio abordado é o controle de estoque, do qual tem por finalidade em
síntese otimizar o investimento em estoques, minimizando a necessidade de capital investido.
Nos artigos anteriores discutimos e implementamos parte do diagrama de classes exibido na
figura 1, ou seja, na segunda parte desta série implementamos no ambiente Delphi as classes:
Endereco, Pessoa, PessoaJ e Empresa; e construímos o formulário principal da aplicação, bem
como o formulário de cadastro do grupo de empresas (view). Com o referido cadastro em
funcionamento podemos cadastrar as empresas que terão controle sobre seus estoques de
produtos, realizando a entrada e saída dos mesmos seja através do formulário de manutenção
de estoque ou mesmo pela entrada de notas fiscais e/ou pela operação de venda ao
consumidor final (estes dois últimos itens citados não fazem parte do escopo do artigo, foram
apenas citados por realizarem operações sobre a quantidade de produtos em estoque). O
propósito agora é implementar as classes: Marca, Unidade, Produto e Estoque; as demais
classes Categoria, Pedido e Entrada não serão implementadas neste momento para não
fugirmos demasiadamente do objetivo do artigo e para simplificar a implementação.
Conforme citado no segundo artigo, uma boa ferramenta para realizar a modelagem de
classes, é o software Jude Community (encontrado em http://jude.change-vision.com/jude-
web/product/community.html), porém infelizmente o projeto desta ferramenta de
modelagem foi descontinuado pela empresa ChangeVision que a mantinha. Os leitores que
tiveram o interesse em baixar a ferramenta e ver como a mesma funciona ficaram frustrados.
Mas nem tudo está perdido, como é de costume a comunidade de desenvolvedores
reivindicou pela ferramenta de modelagem UML e a empresa mantenedora do projeto
retomou este projeto renomeando-o como Astah*Community, que pode ser encontrado em
http://astah.change-vision.com/en/product/astah-community.html.
2. A ferramenta Astah*Community é gratuita, suporta notação UML 2.0, suprindo a necessidade
de grande parte dos elementos necessários no dia-a-dia. O diagrama de classes da figura 1, foi
modelado utilizando o Astah* versão 6.4, caso queira baixar o arquivo do diagrama de classes
do modelo conceitual de controle de estoque, o mesmo pode ser encontrado em
http://ryanpadilha.com.br/downloads/active_delphi/UML_controle_estoque.jude. Seria
interessante baixar o diagrama citado, aprender a utilizar a ferramenta, assim como identificar
os elementos básicos de um diagrama de classe, pois para os leitores que acompanham os
artigos do colunista que os escreve, futuramente abordaremos outros conceitos referentes a
arquitetura e engenharia de software utilizando como base o modelo acima.
É válido observar no diagrama que o elemento Estoque tem sua definição completa, com seus
atributos e métodos. Implementar em Object Pascal esta classe e sua respectiva interação com
um formulário visual é mais trabalhoso, pois se trata de uma operação de movimentação,
frente a implementação de classes de negócios que efetuam apenas operações simples de
CRUD (acrônimo de Create, Retrieve, Update, Delete) como foi abordado na definição dos
elementos Empresa e Endereco.
1.1 O Contexto da classe Estoque
A classe Estoque está associada a classe Empresa, pois uma empresa pode possuir vários
produtos em estoque do qual queira controlar e manter um histórico de entrada e saída do
mesmo. Uma aplicação de controle de estoque pode controlar o estoque de várias empresas,
por isso implementamos um cadastro de grupo de empresas, para que possamos cadastrar as
empresas que controlarão seus estoques. A cardinalidade entre Empresa e Estoque é de 1..*
(um para vários), ou seja, cada empresa controla apenas um único estoque (individualmente),
que é constituído por vários produtos, porém o estoque controlado por cada empresa pode ter
um saldo diferente de um mesmo produto, sendo que há uma separação lógica do mesmo
dentro do banco de dados.
Em relação a classe Produto, esta se relaciona com a classe Estoque através da associação com
cardinalidade 1..* (um para vários), onde para um determinado produto há a ocorrência de
vários registros em um estoque, e o mesmo possui apenas uma referência ao produto.
Resumidamente encontramos um produto com identificação única (através do ID) dentro de
um estoque definindo o tipo de movimentação (E/S) e a quantidade informada pelo usuário da
aplicação. Esta definição ficará mais clara até o final do artigo. O elemento Produto está
associado também a um elemento Marca, cardinalidade de *..1, um produto tem uma única
marca e uma marca pode pertencer a vários produtos; e pode conter várias medidas de
Unidade, cardinalidade de *..*, um produto pode conter várias unidades de medida e as
unidades de medidas pertencem a vários produtos.
Vejamos em mais detalhes o que a classe Estoque possui em sua definição, detalhando as
assinaturas dos métodos implementados: 1) public Validar(): boolean – efetua a validação do
estado do objeto instanciado em memória, tornando-o consistente; 2) public Movimentacao():
boolean – a movimentação de entrada e saída de produtos de uma determinada empresa no
estoque é realizado através da implementação deste método, que é também o principal
método da classe; public ConsultarSaldo(): Double – consulta e retorna o saldo atual de um
produto pertencente ao estoque de determinada empresa (saldo entrada – saldo saída); public
3. getObject(Id: String): boolean – retorna a movimentação do estoque de um produto através de
seu ID (código); public getObjects(): boolean – toda a movimentação efetuada no estoque de
uma empresa é retornado; public getObjects(Id: String): boolean – através deste método é
exibido na Grid do formulário visual (FrmControleEstoque) o histórico de movimentação de um
produto especificado pelo argumento ID (código).
Figura 1 – Diagrama de Classe Simplificado. Domínio: Controle de Estoque.
2. Objetos de negócio e formulários: Divisão de responsabilidades em duas camadas
Este exemplo tem finalidade educacional e pode ser modificado, alterado e distribuído. Caso
seja utilizado para fins didáticos por outras pessoas, preserve o nome do autor.
Adotando o exemplo criado anteriormente na segunda parte da série, continuaremos a
implementar o aplicação de controle de estoque. Para quem irá começar a acompanhar o
desenvolvimento da aplicação deste ponto, o código-fonte de exemplo pode ser encontrado
em http://activedelphi.com.br/downloads/orientacao_objetos.rar. A IDE Delphi 7 Update2
está sendo utilizado no desenvolvimento, pois o objetivo desta série sobre Orientação a
Objetos e seus conceitos adjacentes está voltado a implementação do modelo conceitual e
não focado na utilização de componentes específicos de software. Para realizar a persistência
de dados estamos utilizando o SGBD objeto-relacional PostgreSQL 8.3, que pode ser
encontrado em http://www.postgresql.org.
4. Com o Delphi aberto, procure pelo projeto “ControleEstoque”, que foi salvo anteriormente no
diretório “d:projetosoocontrole_estoque” (ou no diretório de sua preferência), abra-o e o
mesmo será exibido dentro da IDE. O menu principal da aplicação fora definido anteriormente,
restando apenas criar os formulários dos itens de menu. Iremos implementar apenas o
formulário de Cadastro de Produto e o de Movimentação de Estoque, ficando a criação do
restante dos cadastros sugeridos a critério do leitor, pois ao termino deste artigo todos os
princípios básicos de construção de formulários e sua interação com objetos de negócios
(classes) definidos no escopo da aplicação serão abordados.
2.1 Classes de negócio
Seguindo a mesma linha de desenvolvimento, comecemos com a implementação das classes
de negócio, mais especificamente com a classe concreta Unidade; a responsabilidade dessa
classe é conter dados relacionados com as unidades de medidas utilizadas pelos produtos.
Adicione uma nova unit ao projeto através do Menu File – New – Unit, salve-a no diretório
“classes” como clUnidade.pas. No bloco interface/implementation da unit declare/implemente
a classe conforme abaixo:
unit clUnidade;
interface
type
TUnidade = class(TObject)
private
// métodos privados
// métodos acessores suprimidos. getters / setters.
function Insert(): Boolean;
function Update(): Boolean;
protected
// declaração de atributos
_codigo: String;
_descricao: String;
_sigla: String;
public
// declaração das propriedades da classe, encapsulamento de atributos
property Codigo: String read getCodigo write setCodigo;
property Descricao: String read getDescricao write setDescricao;
property Sigla: String read getSigla write setSigla;
// declaração de métodos públicos
function Validar(): Boolean;
function Merge(): Boolean;
function Delete(): Boolean;
function getObject(Id: String): Boolean;
function getObjects(): Boolean;
function getField(campo: String; coluna: String): String;
end;
const
TABLENAME = 'UNIDADE';
5. implementation
uses clUtil, dmConexao, SysUtils, Dialogs;
{ TUnidade }
// implementação suprimida, para não estender muito o artigo
end.
Depois da definição e implementação da classe acima, precisamos agora implementar a classe
Marca que é responsável por conter dados relacionados as marcas dos produtos
comercializados por uma empresa do ramo atacadista/varejista. Adicione uma nova unit ao
projeto através do Menu File – New – Unit, salve-a no diretório “classes” como clMarca.pas.
No bloco interface da unit declare a classe conforme abaixo:
unit clMarca;
interface
type
TMarca = class(TObject)
private
// métodos privados
// métodos acessores suprimidos. getters / setters.
function Insert(): Boolean;
function Update(): Boolean;
protected
// declaração de atributos
_codigo: String;
_descricao: String;
public
// declaração das propriedades da classe, encapsulamento de atributos
property Codigo: String read getCodigo write setCodigo;
property Descricao: String read getDescricao write setDescricao;
// declaração de métodos públicos
function Validar(): Boolean;
function Merge(): Boolean;
function Delete(): Boolean;
function getObject(Id: String): Boolean;
function getObjects(): Boolean;
function getField(campo: String; coluna: String): String;
end;
const
TABLENAME = 'MARCA';
implementation
uses clUtil, dmConexao, SysUtils, Dialogs;
6. { TMarca }
// implementação suprimida, para não estender muito o artigo
end.
Implementamos as classes Unidade e Marca a princípio pois as mesmas estão associadas com
a classe Produto. Na notação UML identificada na figura 1, esta associação possui a definição
de navegabilidade, ou seja, a classe Produto contém referência a objetos do tipo Unidade e
Marca. Logo abaixo na implementação da classe Produto será possível visualizar atributos
declarados com os tipos definidos por nós, e no método construtor da classe a
responsabilidade em instanciar os objetos referenciados. É denotado aqui a interação entre os
objetos de negócio, compondo um rico ambiente onde as diversas unidades de software
(objetos), que inter-relacionadas formam o escopo de uma aplicação, definindo a divisão de
responsabilidades em classes de negócios, resultando em uma coesão satisfatória e uma
granularidade razoável.
Adicione uma nova unit ao projeto através do Menu File – New – Unit, salve-a no diretório
“classes” como clProduto.pas. Nesta unit declaramos a classe conforme abaixo:
unit clProduto;
interface
// utilização das units declaradas anteriormente, classes de negócios
uses clUnidade, clMarca;
type
TProduto = class(TObject)
private
// métodos privados
// métodos acessores suprimidos. getters / setters.
function Insert(): Boolean;
function Update(): Boolean;
protected
// declaração de atributos
_codigo: String;
_codigoPrincipal: String;
_descricao: String;
_descReduzido: String;
_status: Boolean;
_unidade: TUnidade; //
_marca: TMarca; //
// categoria...
public
// método construtor definido por nós
constructor Create;
// declaração das propriedades da classe, encapsulamento de atributos
property Codigo: String read getCodigo write setCodigo;
property CodigoPrincipal: String read getCodigoPrinc write setCodigoPrinc;
property Descricao: String read getDescricao write setDescricao;
7. property DescReduzido: String read getDescReduzido write setDescReduzido;
property Status: Boolean read getStatus write setStatus;
property Unidade: TUnidade read getUnidade write setUnidade;
property Marca: TMarca read getMarca write setMarca;
// na linha acima antes do ponto e virgula (setStatus) pressione Ctrl + Shift + C
// para gerar os métodos acessores getter e setter automaticamente
// declaração de métodos públicos
function Validar(): Boolean;
function Merge(): Boolean;
function Delete(): Boolean;
function getObject(Id: String): Boolean;
function getObjects(): Boolean;
function getField(campo: String; coluna: String): String;
end;
const
TABLENAME = 'PRODUTO';
implementation
uses clUtil, dmConexao, SysUtils, Dialogs;
{ TProduto }
// método construtor
constructor TProduto.Create;
begin
_unidade := TUnidade.Create; // instanciação do objeto Unidade
_marca := TMarca.Create; // instanciação do objeto Marca
end;
// implementação suprimida, para não estender muito o artigo
end.
Através desta série de artigos sobre orientação a objetos no Delphi, adotamos como exemplo
prático o desenvolvimento de uma aplicação de controle de estoque com o intuito de
contemplar a maioria dos conceitos apresentados teoricamente na primeira parte da série. E
neste momento será definido a classe principal de nosso projeto, ou seja, a classe Estoque que
é responsável por controlar o estoque dos produtos de uma determinada empresa através das
operações de entrada e saída. Visualizando a modelagem da figura 1, fica claro que o elemento
Estoque está no cerne do diagrama de classes, pois está relacionado com a classe Produto e
Empresa, que conseqüentemente tem uma rica interação com outros elementos do escopo. É
interessante comentar sobre a associação e navegabilidade entre a classe Estoque-Produto e
Estoque-Empresa, onde em um estoque efetuamos operações de entrada/saída de produtos
definindo a quantidade e valor de custo do mesmo, sobre o estoque de uma determinada
empresa em particular. A descrição sobre a navegabilidade pode ser sintetizada através da
notação de cardinalidade citado logo no início deste artigo.
8. Adicione uma nova unit ao projeto através do Menu File – New – Unit, salve-a no diretório
“classes” como clEstoque.pas. Como esta classe de negócio é o foco principal da aplicação,
será apresentado o código-fonte completo da mesma (exceto os métodos acessores – get/set).
Nesta unit declaramos a classe conforme abaixo:
unit clEstoque;
interface
// utilização das units declaradas anteriormente, classes de negócios
uses clProduto, clEmpresa;
type
TEstoque = class(TObject)
private
// métodos privados
// métodos acessores suprimidos. getters / setters.
protected
// declaração de atributos
_codigo: String;
_data: TDateTime;
_hora: TDateTime;
_documento: String;
_saldo: Double;
_vlrCusto: Currency;
_tipoMov: String;
_quantidade: Double;
_flag: Boolean;
_produto: TProduto;
_empresa: TEmpresa;
public
// método construtor definido por nós
constructor create;
// declaração das propriedades da classe, encapsulamento de atributos
property Codigo: String read getCodigo write setCodigo;
property Data: TDateTime read getData write setData;
property Hora: TDateTime read getHora write setHora;
property Documento: String read getDocumento write setDocumento;
property Saldo: Double read getSaldo write setSaldo;
property VlrCusto: Currency read getVlrCusto write setVlrCusto;
property TipoMov: String read getTipoMov write setTipoMov;
property Quantidade: Double read getQuantidade write setQuantidade;
property Flag: Boolean read getFlag write setFlag;
property Produto: TProduto read getProduto write setProduto;
property Empresa: TEmpresa read getEmpresa write setEmpresa;
// declaração de métodos públicos
function Validar(): Boolean;
function Movimentacao(): Boolean;
function ConsultarSaldo(): Double;
function getObject(Id: String): Boolean;
9. function getObjects: Boolean; overload;
function getObjects(Id: String): Boolean; overload;
end;
const
TABLENAME = 'ESTOQUE';
implementation
uses SysUtils, dmConexao, Dialogs, clUtil, DB, ZAbstractRODataset;
{ TEstoque }
// método construtor
constructor TEstoque.create;
begin
_produto := TProduto.Create; // instanciação do objeto Produto
_empresa := TEmpresa.Create; // instanciação do objeto Empresa
end;
// implementação suprimida dos métodos acessores (get/set), para não estender muito o artigo
// todos os métodos contendo regras de negócios estão relacionados abaixo
function TEstoque.ConsultarSaldo(): Double;
begin
Try
Result := 0;
with Conexao.QryGetObject do begin
Close;
SQL.Clear;
SQL.Text := 'SELECT SUM(EST_QUANTIDADE) - COALESCE((SELECT SUM(EST_QUANTIDADE) AS EST_SALDO FROM
'+ TABLENAME +
' WHERE PRO_CODIGO =:PRODUTO AND EMP_CODIGO =:EMPRESA AND EST_TIPO_MOV = '''+'S'+'''), 0) AS
EST_SALDO '+
' FROM '+ TABLENAME +
' WHERE PRO_CODIGO =:PRODUTO AND EMP_CODIGO =:EMPRESA AND EST_TIPO_MOV = '''+'E'+''' ';
ParamByName('PRODUTO').AsString := Self.Produto.Codigo;
ParamByName('EMPRESA').AsString := Self.Empresa.Codigo;
Open;
First;
end;
if Conexao.QryGetObject.RecordCount > 0 then
Result := Conexao.QryGetObject.FieldByName('EST_SALDO').AsFloat;
Except
on E : Exception do
ShowMessage('Classe: '+ e.ClassName + chr(13) + 'Mensagem: '+ e.Message);
end;
end;
function TEstoque.Movimentacao: Boolean;
begin
// Movimentação de Estoque
10. Try
Result := False;
with Conexao.QryCRUD do begin
Close;
SQL.Clear;
SQL.Text := 'INSERT INTO '+ TABLENAME +' (PRO_CODIGO, EMP_CODIGO, EST_DATA, EST_HORA,
EST_DOCUMENTO, EST_SALDO, '+
' EST_VLR_CUSTO, EST_TIPO_MOV, EST_QUANTIDADE) '+
' VALUES (:PRODUTO, :EMPRESA, :DATA, :HORA, :DOCUMENTO, :SALDO, :VLR_CUSTO, :TIPO_MOV,
:QUANTIDADE)';
ParamByName('PRODUTO').AsString := Self.Produto.Codigo;
ParamByName('EMPRESA').AsString := Self.Empresa.Codigo;
ParamByName('DATA').AsDateTime := Self.Data;
ParamByName('HORA').AsDateTime := Now; // hora atual
ParamByName('DOCUMENTO').AsString := Self.Documento;
// verificando o tipo de movimento
if Self.TipoMov = 'E' then
ParamByName('SALDO').AsFloat := (Self.ConsultarSaldo + Self.Quantidade)
else
ParamByName('SALDO').AsFloat := (Self.ConsultarSaldo - Self.Quantidade);
ParamByName('VLR_CUSTO').AsCurrency := Self.VlrCusto;
ParamByName('TIPO_MOV').AsString := Self.TipoMov;
ParamByName('QUANTIDADE').AsFloat := Self.Quantidade;
ExecSQL;
end;
Result := True;
Except
on E : Exception do
ShowMessage('Classe: '+ e.ClassName + chr(13) + 'Mensagem: '+ e.Message);
end;
end;
function TEstoque.getObjects: Boolean;
begin
Try
Result := False;
with Conexao.QryGetObject do begin
Close;
SQL.Clear;
SQL.Text := 'SELECT * FROM '+ TABLENAME +' ORDER BY EST_CODIGO';
Open;
First;
end;
if Conexao.QryGetObject.RecordCount > 0 then
Result := True;
Except
on E : Exception do
ShowMessage('Classe: '+ e.ClassName + chr(13) + 'Mensagem: '+ e.Message);
11. end;
end;
// pegar movimentação de estoque pelo ID (PRODUTO)
function TEstoque.getObject(Id: String): Boolean;
begin
try
Result := False;
if TUtil.Empty(Id) then
Exit;
with Conexao.QryGetObject do begin
Close;
SQL.Clear;
SQL.Text := 'SELECT * FROM '+ TABLENAME +' WHERE PRO_CODIGO =:CODIGO AND EMP_CODIGO =:EMPRESA';
ParamByName('CODIGO').AsString := Id;
ParamByName('EMPRESA').AsString := Self.Empresa.Codigo;
Open;
First;
end;
if Conexao.QryGetObject.RecordCount > 0 then begin
Self.Codigo := Conexao.QryGetObject.FieldByName('EST_CODIGO').AsString;
Self.Produto.Codigo := Conexao.QryGetObject.FieldByName('PRO_CODIGO').AsString;
Self.Data := Conexao.QryGetObject.FieldByName('EST_DATA').AsDateTime;
Self.Hora := Conexao.QryGetObject.FieldByName('EST_HORA').AsDateTime;
Self.Documento := Conexao.QryGetObject.FieldByName('EST_DOCUMENTO').AsString;
Self.Saldo := Conexao.QryGetObject.FieldByName('EST_SALDO').AsFloat;
Self.VlrCusto := Conexao.QryGetObject.FieldByName('EST_VLR_CUSTO').AsCurrency;
Self.TipoMov := Conexao.QryGetObject.FieldByName('EST_TIPO_MOV').AsString;
Self.Quantidade := Conexao.QryGetObject.FieldByName('EST_QUANTIDADE').AsFloat;
Self.Flag := Conexao.QryGetObject.FieldByName('EST_FLAG').AsBoolean;
Result := True;
end;
Except
on E : Exception do
ShowMessage('Classe: '+ e.ClassName + chr(13) + 'Mensagem: '+ e.Message);
end;
end;
function TEstoque.Validar: Boolean;
begin
// validação de atributos
if Length(DateToStr(_data)) <> 10 then begin
ShowMessage('Data Inválida!');
Result := false;
Exit;
end
else if _quantidade = 0 then begin
ShowMessage('Quantidade Inválida!');
Result := False;
Exit;
end;
12. Result := True;
end;
function TEstoque.getObjects(Id: String): Boolean;
begin
Try
Result := False;
with Conexao.QryEstoque do begin
Close;
SQL.Clear;
SQL.Text := 'SELECT * FROM '+ TABLENAME +' WHERE PRO_CODIGO =:CODIGO AND EMP_CODIGO =:EMPRESA
ORDER BY EST_CODIGO';
ParamByName('CODIGO').AsString := Id;
ParamByName('EMPRESA').AsString := Self.Empresa.Codigo;
Open;
First;
end;
if Conexao.QryGetObject.RecordCount > 0 then begin
Self.getObject(Id);
Result := True;
end;
Except
on E : Exception do
ShowMessage('Classe: '+ e.ClassName + chr(13) + 'Mensagem: '+ e.Message);
end;
end;
end.
Agora que temos o diagrama de classes representado pela figura 1 implementado totalmente
em Object Pascal (exceto a classe Categoria), vamos construir o formulário que irá persistir os
objetos do tipo Produto e outro que efetue o controle de estoque, através da movimentação
de produtos operando a entrada e saída de produtos, atualizando seu saldo corrente.
2.2 A camada de visualização de dados: O formulário
Utilizando o conceito RAD (Rapid Application Development - Desenvolvimento Rápido de
Aplicativos) da IDE Delphi construímos formulários visuais com rapidez e facilidade, obtemos
uma velocidade ainda maior no desenvolvimento pois a implementação de toda a lógica da
aplicação já está definida. Com isso os formulários passam a atuar definitivamente apenas
como instrumentos de entrada e visualização de dados, tendo sua responsabilidade resumida
a isto.
Não é necessário especificar todos os componentes visuais e suas respectivas propriedades,
estou assumindo que a maioria dos leitores possui plenos conhecimentos em construção de
formulários visuais utilizando o Delphi. Para a construção do formulário de Cadastro de
Produtos tomemos como base o layout sugerido pela figura 2. E para o formulário principal da
aplicação, o de Controle de Estoque adotemos o layout proposto pela figura 3.
13. Figura 2 – Formulário de Cadastro de Produto.
No formulário da figura 2, podemos utilizar a classe Produto declarando uma variável do tipo
TProduto no bloco interface/var da unit. Todos os dados digitados no formulário são atribuídos
as propriedades da classe Produto, alterando o estado do objeto instanciado, que é então
persistido no SGBD. Toda a interação efetuada com o formulário ocorre através de eventos,
que quando disparados executam algum procedimento dentro da aplicação. Para exemplificar
o que acabados de descrever, ao clicarmos sobre o botão Salvar (ícone disquete), é invocado o
evento onClick sppSalvarClick(Sender: TObject) que contempla a seguinte declaração:
procedure TFrmCadastroProduto.sppSalvarClick(Sender: TObject);
var
Operacao: String;
begin
Operacao := 'U';
// atribuindo os valores digitados no formulário as propriedades do objeto Produto
Produto.Codigo := edtCodigo.Text;
Produto.CodigoPrincipal := edtCodigoPrinc.Text;
Produto.Descricao := edtDescricao.Text;
Produto.DescReduzido := edtDescRed.Text;
if cmbStatus.ItemIndex = 0 then
Produto.Status := false
else
Produto.Status := true;
if TUtil.Empty(Produto.Codigo) then
Operacao := 'I';
14. if Produto.Validar() then
if Produto.Merge() then begin
if Operacao = 'I' then
ShowMessage('Registro Gravado com Sucesso!')
else
ShowMessage('Registro Atualizado com Sucesso!');
Conexao.QryProduto.Close;
Conexao.QryProduto.Open;
sppCancelarClick(Self);
end;
end;
Antes mesmo da chamada do método Merge() que é responsável por persistir o objeto no
banco de dados, as propriedades do objeto são alteradas através da atribuição dos valores
digitados no formulário. Notamos que o formulário apenas “repassa” os valores ao objeto e o
mesmo se encarrega de validar e persistir os dados efetivamente. O que acabamos de
descrever aqui é um modelo de divisão de responsabilidade onde cada unidade de software é
responsável por determinada atividade.
Temos mais dois eventos declarados no formulário da figura 2 que serão abordados – o
procedimento privado onClick sppCancelarClick(Sender: TObject) que limpa os campos do
formulário, destrói e instancia novamente o objeto do tipo Produto, renovando seu estado.
Quando clicamos no botão cancelar (ícone X), o seguinte procedimento é executado:
procedure TFrmCadastroProduto.sppCancelarClick(Sender: TObject);
begin
TUtil.LimparFields(FrmCadastroProduto);
Produto := nil;
Produto := TProduto.Create;
edtDescricao.SetFocus;
end;
O segundo evento citado no parágrafo acima é o procedimento onClick sppExcluirClick(Sender:
TObject) que deleta um objeto Produto da tabela relacional se a propriedade código do objeto
instanciado em memória estiver com valor definido e for consistente. Quando clicamos no
botão excluir (ícone lixeira), o seguinte evento é disparado:
procedure TFrmCadastroProduto.sppExcluirClick(Sender: TObject);
begin
if TUtil.Empty(Produto.Codigo) then
Exit;
if MessageBox(Application.Handle, 'Deseja Realmente Excluir o Registro?', 'Controle Estoque', MB_ICONQUESTION
+ MB_YESNO + MB_DEFBUTTON2) = ID_NO then
Exit;
if TUtil.NotEmpty(Produto.Codigo) then begin
if NOT Produto.Delete then
ShowMessage('Erro ao Excluir o Registro.')
else begin
TUtil.LimparFields(FrmCadastroProduto);
15. Conexao.QryProduto.Close;
Conexao.QryProduto.Open;
end;
end;
end;
Com o cadastro de produto em pleno funcionamento podemos realizar operações CRUD com o
mesmo, necessitando a princípio inserir produtos que serão controlados pelo formulário da
figura 3, o controle de estoque. Neste formulário selecionamos a empresa da qual estamos
controlando o estoque de produtos, cada empresa possui um estoque independente conforme
citado anteriormente. No campo “Código do Produto” digitamos o código do produto do qual
queiramos pesquisar e visualizar o histórico de movimentação de entrada e saída, assim como
seu saldo atual em estoque. Após um produto ser encontrado, seu estado é recuperado do
banco de dados e definido na instancia do objeto em memória (objeto Estoque.Produto), o
componente visual groupBox de movimentação é exibido, permitindo a movimentação de
Entrada(1) ou Saída(2) do produto – informando uma quantidade (número de ponto flutuante
positivo), valor monetário de custo (compra), número do documento do qual origina-se o
movimento. Ao clicar no tipo de movimento (componente radioGroup) o botão movimentar é
ativado e sua propriedade caption é modificada de acordo com o tipo selecionado. Ao clicar no
referido botão o seguinte evento é disparado:
procedure TFrmControleEstoque.btnMovimentarClick(Sender: TObject);
begin
if MessageBox(Application.Handle, 'Deseja Realmente Movimentar o Estoque?', 'Controle Estoque',
MB_ICONQUESTION + MB_YESNO + MB_DEFBUTTON2) = ID_NO then
Exit;
case rdgTipo.ItemIndex of
0 : Estoque.TipoMov := 'E'; // entrada
1 : Estoque.TipoMov := 'S'; // saída
end;
// alterando o estado do objeto Estoque
Estoque.Data := StrToDate(mskData.Text);
Estoque.Quantidade := StrToFloat(edtQuantidade.Text);
Estoque.VlrCusto := StrToFloat(edtValorCusto.Text);
Estoque.Documento := edtDocumento.Text;
if Estoque.Validar() then
if Estoque.Movimentacao() then
LimparMovimento
else
ShowMessage('Problemas ao Efeturar Movimentação no Estoque!');
end;
16. Figura 3 – Formulário de Controle de Estoque (movimentação).
O método Movimentacao(): boolean é o principal método definido no escopo da aplicação,
executando a movimentação de estoque, atualizando o saldo em estoque do produto
selecionado. Caso tenha alguma dúvida em como a regra está implementada na classe
clEstoque.pas verifique novamente a declaração do código da classe definida anteriormente. O
formulário da figura 3 pode ser invocado também através do cadastro de produtos, clicando
no botão “Verificar Estoque”; se algum produto estivar selecionado, ou seja, sendo visualizado
no cadastro de produto, o mesmo será passado por referência ao objeto declarado como
public ProdutoRef na unit do formulário de controle de estoque. O evento onClick do botão
citado é definido na unit untCadastroProduto.pas como:
procedure TFrmCadastroProduto.btnEstoqueClick(Sender: TObject);
begin
if NOT Assigned(FrmControleEstoque) then
FrmControleEstoque := TFrmControleEstoque.Create(Application);
// passando o Objeto Produto para o outro formulário (estoque)
// em cada formulário encontramos um objeto do tipo produto
// sendo possível passar o estado de um objeto de um formulário
// ao objeto de outro formulário
if TUtil.NotEmpty(Produto.Codigo) then
FrmControleEstoque.ProdutoRef := Produto;
FrmControleEstoque.ShowModal;
end;
17. O processo de transferir o estado de um objeto instanciado em um formulário a outro é
comumente utilizado e de simples utilização. Este procedimento é utilizado também em
formulários de pesquisa, que geralmente são invocados por formulários de cadastro.
Na segunda parte da série criamos o banco de dados “ActiveDelphi” através da ferramenta
gráfica pgAdminIII (acompanha a instalação do SGBD PostgreSQL), execute-a, com ela aberta
dê dois cliques no hostname onde o banco de dados fora criado, realize o login, forneça
apenas a senha que foi definida ao usuário do banco. No item Banco de Dados, expanda a lista
de bancos de dados existentes e procure pelo nome “ActiveDelphi”, selecione-o; através do
editor SQL (clicando no ícone SQL) execute o seguinte script SQL DDL (Data Definition
Language):
-- MARCA
CREATE TABLE MARCA(
MAR_CODIGO SERIAL NOT NULL,
MAR_DESCRICAO VARCHAR(100) NOT NULL
);
ALTER TABLE MARCA ADD CONSTRAINT PK_MARCA PRIMARY KEY(MAR_CODIGO);
-- POPULANDO A TABELA MARCA
INSERT INTO MARCA (MAR_DESCRICAO) VALUES ('DUREX');
INSERT INTO MARCA (MAR_DESCRICAO) VALUES ('EATON');
INSERT INTO MARCA (MAR_DESCRICAO) VALUES ('INDISA');
INSERT INTO MARCA (MAR_DESCRICAO) VALUES ('LUCIFLEX');
INSERT INTO MARCA (MAR_DESCRICAO) VALUES ('PERKINS');
INSERT INTO MARCA (MAR_DESCRICAO) VALUES ('STAHL');
-- UNIDADE
CREATE TABLE UNIDADE(
UND_CODIGO SERIAL NOT NULL,
UND_DESCRICAO VARCHAR(100) NOT NULL,
UND_SIGLA VARCHAR(5) UNIQUE NOT NULL
);
ALTER TABLE UNIDADE ADD CONSTRAINT PK_UNIDADE PRIMARY KEY(UND_CODIGO);
-- POPULANDO A TABELA UNIDADE
INSERT INTO UNIDADE (UND_DESCRICAO, UND_SIGLA) VALUES ('CAIXA', 'CXA');
INSERT INTO UNIDADE (UND_DESCRICAO, UND_SIGLA) VALUES ('UNIDADE', 'UND');
INSERT INTO UNIDADE (UND_DESCRICAO, UND_SIGLA) VALUES ('KILO', 'KG');
INSERT INTO UNIDADE (UND_DESCRICAO, UND_SIGLA) VALUES ('METRO', 'MT');
-- PRODUTO
CREATE TABLE PRODUTO(
PRO_CODIGO SERIAL NOT NULL,
PRO_C_PRINCIPAL VARCHAR(20),
PRO_STATUS BOOLEAN,
PRO_DESCRICAO VARCHAR(200),
18. PRO_DESC_REDUZIDO VARCHAR(50),
MAR_CODIGO INTEGER,
UND_CODIGO INTEGER
);
ALTER TABLE PRODUTO ADD CONSTRAINT PK_PRODUTO PRIMARY KEY(PRO_CODIGO);
ALTER TABLE PRODUTO ADD CONSTRAINT FK_PRODUTO_MARCA FOREIGN KEY(MAR_CODIGO)
REFERENCES MARCA(MAR_CODIGO);
ALTER TABLE PRODUTO ADD CONSTRAINT FK_PRODUTO_UNIDADE FOREIGN KEY(UND_CODIGO)
REFERENCES UNIDADE(UND_CODIGO);
-- ESTOQUE
CREATE TABLE ESTOQUE(
EST_CODIGO SERIAL NOT NULL,
PRO_CODIGO INTEGER NOT NULL, -- PRODUTO
EMP_CODIGO INTEGER NOT NULL, -- EMPRESA
EST_DATA DATE NOT NULL,
EST_HORA TIME NOT NULL,
EST_DOCUMENTO VARCHAR(20),
EST_SALDO NUMERIC(15,5),
EST_VLR_CUSTO NUMERIC(15,5),
EST_TIPO_MOV CHAR,
EST_QUANTIDADE NUMERIC(15,5),
EST_FLAG BOOLEAN
);
ALTER TABLE ESTOQUE ADD CONSTRAINT PK_ESTOQUE PRIMARY KEY(EST_CODIGO);
ALTER TABLE ESTOQUE ADD CONSTRAINT FK_ESTOQUE_PRODUTO FOREIGN KEY(PRO_CODIGO)
REFERENCES PRODUTO(PRO_CODIGO);
ALTER TABLE ESTOQUE ADD CONSTRAINT FK_ESTOQUE_EMPRESA FOREIGN KEY(EMP_CODIGO)
REFERENCES EMPRESA(EMP_CODIGO);
Terminamos neste momento a aplicação de controle de estoque, que poderá ser baixada na
integra em http://ryanpadilha.com.br/downloads/active_dephi/controle_estoque_parte2.rar.
Caso algum leitor tenha alguma sugestão, crítica ou elogio referente ao que foi abordado e
implementado aqui, entre em contato com este colunista que vos escreve.
Esta aplicação utiliza componentes TEdit que não possuem vínculo direto com um DataSet em
particular, ou seja, com acesso direto ao banco de dados tal como os componentes do estilo
TDBEdit. Então você está livre para alterá-lo conforme a sua necessidade e vontade.
2.3 Justificativa da arquitetura em duas camadas
Alguns leitores podem argumentar neste momento: Em aplicações que utilizam o paradigma
estruturado, o formulário trata os dados digitados, não repassa informação a nenhum outro
lugar, apenas persiste a informação em uma tabela relacional utilizando componentes de
acesso direto a banco de dados (os famosos componentes DB<objeto>), a unit do formulário
contem toda a lógica, resumindo é muito rápido desenvolver software assim. E com a
arquitetura proposta neste artigo temos a divisão de responsabilidades em duas camadas
19. (model e view), onde a implementação fica separada uma da outra e que no final da um pouco
mais de trabalho em implementar. Afinal o que ganhamos com a utilização desse modelo de
duas camadas ?
Sinceramente este questionamento ocorre com freqüência, a maioria é feita por
desenvolvedores originados do modelo estrutural de desenvolvimento. A orientação a objetos
é uma evolução conceitual dentro do ramo da engenharia de software que vem se mostrando
cada dia mais poderosa e flexível. Porém não é a palavra final tratando-se de desenvolvimento
de software, com certeza a evolução caminha e novas tecnologias e conceitos surgem,
complementando o modelo anteriormente proposto pelos pesquisadores e engenheiros de
software.
Retornando a pergunta provavelmente feita por muitos leitores, temos vários conceitos
envolvidos neste modelo proposto: 1) O princípio de responsabilidade única (SRP – Single
Responsability Principle) – onde cada classe ou até mesmo unit detêm responsabilidade
exclusiva dentro do contexto da aplicação, aumentando significativamente a coesão das
unidades de software; 2) Arquitetura de software em camadas – a manutenção do software
que corresponde em média a 80% do investimento é facilitado pois temos divisão de
implementação em camadas, reduzindo o acoplamento, ou seja, tornando as classes/units
mais dependentes umas das outras; 3) Nível satisfatório de coesão e acoplamento – através da
definição de classes coesas fica mais fácil de manter e estender suas funcionalidade assim
como mantemos um conjunto de classes de regras de negócios que são auto-independentes
da plataforma, ou seja, podemos ter as mesmas classes de negócios com apenas a camada de
visualização (view) distintas – desktop ou intraweb, pois as temos um baixo acoplamento entre
as camadas.
3. Conclusão
Esta série de três partes sobre o paradigma orientado a objetos tem seu encerramento no
fechamento deste artigo, do qual teve como objetivo revisar tudo o que fora visto até então
sobre os conceitos teóricos e práticos e reforçar o que ainda não tinha sido abordado.
Introduzimos o leitor no “mundo OO” exibindo suas vantagens e desvantagens, as dificuldades
encontradas por programadores que desejam iniciar-se neste contexto e as tecnologias
adjacentes que auxiliam o desenvolvedor a alcançar a qualidade de software. A teoria
apresentada fora implementada na prática, comprovando que é possível sim desenvolver
aplicativos robustos e flexíveis, orientados a objeto, utilizando o Object Pascal que é pouco
difundido entre os desenvolvedores da comunidade Delphi, pois há uma carência de material
de qualidade sobre o assunto.
A aplicação de controle de estoque teve sua arquitetura definida em duas camadas
(model/view), objetivando uma alta coesão e baixo acoplamento entre unidades de software,
porém em uma medida satisfatória de granularidade. Este conceito de duas camadas pode ser
substituída pelo padrão de projeto MVC (model/view/controller) melhorando o escopo de
controle de estoque adotado. Em um próximo artigo veremos conceitualmente como este
padrão de projeto opera e como pode ser implementado na prática sem maiores dificuldades.
20. Fico contente e gratificado em ter ajudado os leitores a esclarecer suas dúvidas sobre
orientação a objetos. A área de Engenharia de Software é ampla e contempla muitos conceitos
e práticas das quais serão abordadas em outras oportunidades. Caso algum leitor tenha
sugestões de artigos sobre a área citada entre em contato comigo. Será uma honra escrever
novos artigos sobre arquitetura e engenharia.
Caso tenha alguma dúvida entre em contato comigo. Será um prazer ajudar.
Forte Abraço a todos os leitores que acompanharam a série Delphi OO – teórico e prático!