arquitetura mvc delphi
DESCRIPTION
Introdução a arquitetura mvc.TRANSCRIPT
Design Pattern MVC – Arquitetura de Software Coesa e Flexível
Ryan Bruno C Padilha [email protected]
http://ryanpadilha.com.br
Objetivo deste artigo
O objetivo deste artigo é fornecer uma visão geral da utilização do design pattern MVC -‐ Model-‐View-‐Controller no Object Pascal, que é um padrão de projeto arquitetural utilizado como boa prática na construção de softwares orientados a objetos reutilizáveis e eficientes. Idealizado por Trygve Reenskaug no final dos anos 70, foi na época uma das maiores contribuições na área de desenvolvimento de software de todos os tempos. Objetiva a organização da aplicação em camadas – separando a lógica de negócio da camada de apresentação utilizando como mediador um controlador. Na série de artigos sobre o paradigma orientado a objetos escrito por mim e publicado nas edições 86 a 88, fora desenvolvido um módulo de controle de estoque do qual continha um cadastro de empresa, este cadastro será remodelado aplicando o design pattern MVC e para simplificar o desenvolvimento reforçaremos novamente a utilização da notação UML, que oferece uma documentação padronizada do projeto de software e como seus elementos interagem entre si.
1. Introdução
O conceito padrão de projeto foi utilizado pela primeira vez na década de 70 pelo arquiteto e urbanista austríaco Christopher Alexander. Ele observou que as construções embora fossem diferentes em vários aspectos, todas passavam pelos mesmos problemas na hora de se construir. Eram problemas que se repetiam em todas elas e na mesma fase de construção. Com isso Christopher iniciou o processo de documentar estes problemas e as soluções que eram aplicadas para resolução destes problemas. Até nesse ponto na história, não há nada de desenvolvimento de software e sim o surgimento de padrões de projetos para a engenharia civil, padrões que descrevem os problemas recorrentes em um projeto de engenharia e a solução reutilizável para este problema.
Paralelamente a isso no final da década de 70, exatamente em 1978 – no Xerox Palo Alto Research Laboratory (PARC), o cientista Trygve Reenskaug iniciou a escrita do mais importante artigo para a engenharia de software intitulado a princípio como Model-‐View-‐Editor, porém foi descrito em sua primeira dissertação como “Thing-‐Model-‐View-‐Editor – an Example from a planningsystem” (Maio de 1979). Após longas discussões, particularmente com Adele Golberg, finalmente apresentou o termo acima como Model-‐View-‐Controllers, descrito em sua segunda dissertação (Dezembro de 1979). A primeira versão do padrão MVC foi implementada na biblioteca de classes do Smalltalk-‐80 logo após Trygve deixar o Xerox PARC, não trabalhou diretamente neste projeto, sua contribuição foi através das dissertações. Para ter acesso as publicações de Trygve, acesse http://heim.ifi.uio.no/~trygver/themes/mvc/mvc-‐index.html.
O MVC até aquele momento tinha sido definido como um padrão arquitetural para o desenvolvimento de softwares orientados a objetos, o termo design patterns (padrões de projetos) somente foi definido anos mais tarde dentro da área da ciência da computação.
Na engenharia de software, os design patterns tiveram origem através do GoF (Gang of Four – Gangue dos quatro), composta por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides que iniciaram suas pesquisar com base no trabalho de Christopher Alexander. Este foi um importante processo pois começaram a descrever e documentar os problemas comuns e soluções recorrentes para o desenvolvimento de software orientado a objetos, assim como tinha feito o austríaco anteriormente. Ao final do trabalho de pesquisa e documentação, em Outubro de 1994 foi lançado o livro “Design Patterns: Elements of Reusable Object -‐Oriented Software” – traduzido para o português como “Padrões de Projetos – Soluções reutilizáveis de software orientado a objetos”.
A partir daquele momento, engenheiros e desenvolvedores de software possuíam um livro sobre padrões de projetos que se dividem em três categorias: criacional, estrutural e comportamental somando 23 no total, e que podem ser adotados no desenvolvimento de software orientado a objetos, resultando em uma maior qualidade nos produtos desenvolvidos.
2. Design Pattern MVC
A arquitetura de software em camadas não é algo novo como fora visto no tópico anterior, mesmo assim vários desenvolvedores ainda tem dificuldades e dúvidas em relação a aplicação deste conceito. Por conseguinte, é interessante explorar o padrão de projeto MVC e como implementá-‐lo no Object Pascal, analisando as suas vantagens e desvantagens. Com a arquitetura do software organizada em camadas, resultando na divisão de responsabilidades entre classes, ou seja, pode-‐se definir vários pacotes (diretórios) e organizar as classes nestes levando em consideração sua finalidade. Porém separar as classes em pacotes e organizar em camadas não promove uma arquitetura MVC, o principal objetivo é separar a lógica do negócio da camada de apresentação utilizando como mediador um controlador. Para ficar mais claro ao leitor, vamos ilustrar o objetivo do padrão MVC através da figura 1 – as linhas sólidas representam associação direta e as tracejadas associação indireta.
Figura 1 – Diagrama simples exemplificando a relação entre Model, View e Controller.
Resumidamente o enfoque é na divisão lógica da aplicação em camadas objetivando uma alta coesão, baixo acoplamento e separação de responsabilidades. Quando dialogamos sobre alta coesão nos referimos a quanto uma classe é coesa, ou seja, ela deve possuir apenas uma responsabilidade e “não fazer coisas demais”. Por exemplo, se sua responsabilidade é o acesso
a banco de dados, a classe deve conter somente métodos que possuam este propósito, nada além disso. Em um projeto de software haverá classes com outras responsabilidades, como de implementar métodos de regras de negócio, outras com a finalidade de exibir e obter os dados do usuário, e por fim classes responsáveis por controlar (comportando-‐se como um middleware) o acesso do usuário a camada de domínio (model), e conseqüentemente realizar acesso a um banco de dados (SGBD). É notável a separação de responsabilidades entre as diversas classes organizadas logicamente em pacotes, quanto mais coesas forem as classes mais serão independentes umas das outras, sendo reforçado o conceito do baixo acoplamento – isolar as alterações (manutenção) que são realizadas em uma camada e que não afetam diretamente as outras. Esta independência pode proporciona uma maior reutilização de código; como estão acopladas de forma fraca, se desejarmos é possível apenas substituir a camada de visualização e manter todo o resto em perfeito funcionamento. Um exemplo prático disto seria que um mesmo código-‐fonte pode rodar no ambiente win32 (desktop) e no intraweb (internet) apenas modificando a camada de visualização, pois esta última faz uso de uma classe controladora para acessar a regra de negócio, não possui acesso direto as métodos que processam a lógica da aplicação. O usuário tem a “falsa impressão” de acessar e manipular o domínio da aplicação de forma direta, como exibido na figura 2.
Figura 2 – Solução MVC ideal suporta a ilusão de estar acessando o domínio diretamente.
Para proporcionar uma fácil visualização de como a aplicação está modelada e mapear a seqüencia de atividades processadas quando há interação do usuário com a mesma, visualize o diagrama de seqüencia (documentação UML) apresentado na figura 3 que define o ciclo de vida de uma solicitação, ou seja, quando o usuário clicar sobre um botão encontrado no formulário (view), é disparado um evento com a seguinte seqüencia de atividades mapeadas no diagrama.
Figura 3 – Diagrama de Seqüencia. Interação do usuário com o software.
É notado uma complexidade maior na adoção do padrão de projeto MVC em relação as duas camadas vistas na série sobre orientação a objetos no Delphi (Revista Active Delphi , edição 86 a 88). O propósito deste artigo é reutilizar o que fora visto nos artigos citados, aplicar e implementar o MVC no domínio referente ao cadastro do grupo de empresas. O leitor foi conduzido a princípio a uma implementação de diagrama de classes de fácil entendimento e codificação, pois anteriormente possuíamos somente duas camadas de software, a de visualização (view/formulário) e a de regra de negócios somada com acesso a dados do SGBD (model + DAO). Neste artigo é definido separadamente, além das camadas citadas, a camada do controlador (controller) e a de acesso ao banco de dados (DAO). Os detalhes das duas novas camadas introduzidas com a arquitetura MVC neste contexto é explicada logo em seguida.
O controlador é responsável por moderar todos os eventos disparados pelo usuário da aplicação, ou seja, para visualizar o cadastro de empresa o usuário pode clicar no item de menu referente ao cadastro desejado, o mesmo será exibido na tela através da instanciação de um objeto do tipo Controller. Em seu método construtor, a inicialização do formulário ocorre, define-‐se todos os eventos que ele deve possuir. Se o usuário desejar inserir um novo registro no banco de dados, um evento é disparado na view ao clicar no botão gravar, que posteriormente é repassado ao controlador daquela formulário e só então decidido o que fazer em relação à aquele evento, pois a partir daqui a regra de negócio pode ser processada e se necessário realizar a recuperação/persistências dos dados no SGBD.
A camada DAO (Data Access Object) é responsável por realizar o acesso ao banco de dados. Com a definição de uma camada de acesso direto ao SGBD independente, é possível trabalhar com diversos bancos de dados diferentes, tendo uma classe DAO para cada banco de dados específico, tornamos a aplicação multi-‐banco de forma rápida e fácil.
Entender a lógica simplificada de funcionamento do design pattern MVC é fundamental, veja como é possível implementá-‐lo no Object Pascal no próximo tópico.
2.1 Arquitetura MVC no Delphi
O diagrama de classes apresentado na figura 4 representa a modelagem de um cadastro de grupo de empresa, cada classe está contida dentro de um determinado pacote. O formulário de cadastro (view -‐ apresentação) é invocado pela classe EmpresaCTR (controller -‐ controlador) que então instância um objeto do tipo Empresa (model – modelo negócios) e EmpresaDAO (DAO – acesso a dados). Observa-‐se que o controller é o elemento com inteligência suficiente para “orquestrar” o conjunto de classes associadas de alguma forma a ele. Os eventos do tipo onClick de TButton (botões) do formulário estão definidos e implementados no controlador, deixa-‐se na view apenas os métodos e eventos referentes ao próprio formulário. A camada de visualização não detêm quase nenhum código, sua responsabilidade de fato é somente obter e exibir o estado dos objetos da camada model.
Figura 4 – Diagrama de Classe. Domínio: Cadastro de Empresa. Contexto: Controle de Estoque.
No formulário da aplicação de controle de estoque (FrmPrincipal), ao clicar no menu principal “Cadastros – Grupo de Empresas”, o cadastro é exibido conforme apresentado na figura 5. Este formulário fora construído anteriormente, sendo necessário neste momento a remoção de todo o código referente aos eventos onClick de TButton – dos botões: salvar, cancelar, excluir e pesquisar – repassando está responsabilidade para a camada controller, ou seja, para a classe EmpresaCTR. Esta classe possui métodos para cada evento retirado da camada de visualização.
Figura 5 – Camada de Apresentação (view). Formulário: Cadastro Grupo de Empresa.
Conforme citado anteriormente, ao clicar no menu principal o formulário da figura 5 é exibido, porém fizemos uma pequena modificação no procedimento deste item de menu, logo a responsabilidade é do controlador em instanciar e exibir o formulário na tela. Note que sem o padrão MVC o formulário era invocado diretamente. Agora com a adoção do MVC, deve-‐se instanciar um objeto do tipo TEmpresaCTR. A grande “mágica” está no método construtor do objeto EmpresaCTR, responsável por instanciar objetos associados e inicializar o formulário atribuindo os eventos onClick de TButton. Veja como é realizada a instanciação do objeto EmpresaCTR logo abaixo:
procedure TFrmPrincipal.GrupodeEmpresas1Click(Sender: TObject); var EmpresaCTR : TEmpresaCTR; begin
{ -‐-‐ Invocação de Formulário sem o Padrão MVC if NOT Assigned(FrmCadastroEmpresa) then FrmCadastroEmpresa := TFrmCadastroEmpresa.Create(Application); FrmCadastroEmpresa.ShowModal; } // Aplicação do Padrão de Projeto MVC // controller -‐ Empresa EmpresaCTR := TEmpresaCTR.Create; end; No método construtor Create de TEmpresaCTR é realizado a chamada ao método Inicializar().É instanciado um objeto do tipo TEmpresa e TEmpresaDAO, que são utilizados durante o clico de vida do controlador, e o formulário associado. Veja o código-‐fonte referente ao método Inicializar():
procedure TEmpresaCTR.Inicializar; begin // Instanciação do objeto Empresa e EmpresaDAO na memória HEAP! Empresa := TEmpresa.Create; EmpresaDAO := TEmpresaDAO.Create; // Camada de Visualização -‐ Formulário if NOT Assigned(FrmCadastro) then FrmCadastro := TFrmCadastroEmpresa.Create(Application); FrmCadastro.sppSalvar.OnClick := SalvarClick; FrmCadastro.sppCancelar.OnClick := CancelarClick; FrmCadastro.sppExcluir.OnClick := ExcluirClick; FrmCadastro.sppCancelar.OnClick := CancelarClick; FrmCadastro.ShowModal; // exibir na tela end; É importante observar no diagrama que o elemento Empresa tem sua definição completa, com seus atributos e métodos, nada além disto. As operações CRUD (acrônimo de Create, Retrieve, Update, Delete) que são operações com acesso ao banco de dados estiveram anteriormente “misturadas” com métodos de regras de negócio, ou seja, a classe possuía duas responsabilidades em sua implementação. Argumentamos, quando há em uma classe mais do que uma responsabilidade, ela não está coesa. O recomendado é deixar somente a regra de negócios na classe Empresa, da camada model, e as operações CRUD devem ser implementadas na classe EmpresaDAO, da camada DAO. Para que a interação entre os objetos esteja precisa, devemos alterar a assinatura dos métodos que realizam o CRUD. Para maiores detalhes veja novamente o diagrama de classe da figura 4.
O procedimento SalvarClick declarado anteriormente na camada view, está agora sob a responsabilidade do controlador, e apresenta a seguinte implementação:
procedure TEmpresaCTR.SalvarClick(Sender: TObject); var Operacao: String; begin Operacao := 'U'; Empresa.Codigo := Self.FrmCadastro.edtCodigo.Text; Empresa.Razao := Self.FrmCadastro.edtRazao.Text; Empresa.Fantasia := Self.FrmCadastro.edtFantasia.Text; Empresa.DtAbertura := StrToDate(Self.FrmCadastro.mskAbertura.Text); Empresa.DtCadastro := StrToDate(Self.FrmCadastro.mskCadastro.Text); Empresa.Status := Self.FrmCadastro.cmbStatus.ItemIndex; Empresa.Alias := Self.FrmCadastro.edtAlias.Text; // telefones Empresa.Telefone := Self.FrmCadastro.mskTelefone.Text; Empresa.FoneFax := Self.FrmCadastro.mskFoneFax.Text; // documentos Empresa.CNPJ := Self.FrmCadastro.mskCNPJ.Text; Empresa.IE := Self.FrmCadastro.edtIE.Text; Empresa.IM := Self.FrmCadastro.edtIM.Text; Empresa.IEST := Self.FrmCadastro.edtIEST.Text; Empresa.Email := Self.FrmCadastro.edtEmail.Text; Empresa.Pagina := Self.FrmCadastro.edtPagina.Text; Empresa.Crt := Self.FrmCadastro.cmbCRT.ItemIndex; Empresa.Cnae := Self.FrmCadastro.edtCNAE.Text; Empresa.Contato := Self.FrmCadastro.edtContato.Text; Empresa.Observacao := Self.FrmCadastro.memoObservacao.Text; Empresa.Contato := Self.FrmCadastro.edtContato.Text; // endereco Empresa.Endereco.Correspondencia := Self.FrmCadastro.chkEnderecoCorresp.Checked; Empresa.Endereco.Logradouro := Self.FrmCadastro.edtLogradouro.Text; Empresa.Endereco.Numero := Self.FrmCadastro.edtNumero.Text; Empresa.Endereco.Complemento := Self.FrmCadastro.edtComplemento.Text; Empresa.Endereco.Bairro := Self.FrmCadastro.edtBairro.Text; Empresa.Endereco.Cidade := Self.FrmCadastro.edtCidade.Text; Empresa.Endereco.Cep := Self.FrmCadastro.mskCEP.Text; Empresa.Endereco.Referencia := Self.FrmCadastro.edtPontoReferencia.Text; Empresa.Endereco.Tipo := Self.FrmCadastro.cmbTipoEndereco.ItemIndex;
if TUtil.Empty(Empresa.Codigo) then Operacao := 'I'; if Empresa.Validar() then if EmpresaDAO.Merge(Empresa) then begin TUtil.LimparFields(Self.FrmCadastro); if Operacao = 'I' then ShowMessage('Registro Gravado com Sucesso!') else ShowMessage('Registro Atualizado com Sucesso!'); Self.FrmCadastro.edtFantasia.SetFocus; end; end; Observe que a única linha de código que foi alterada acima é a chamada do método Merge, pois sua implementação encontra-‐se agora na classe EmpresaDAO. Através deste método é persistido o estado do objeto Empresa instanciado e utilizado pela camada de visualização para a recepção/atualização dos dados. Antes da serialização dos dados no banco, atribuímos os valores dos campos do formulário para os respectivos atributos do objeto Empresa. Logo devemos passar o mesmo como parâmetro para ser persistido no SGBD. O método Merge(Empresa: TEmpresa), recebe o objeto Empresa como argumento e verifica se o atributo código está “setado”, caso não esteja, significa que é um objeto sem ID (ainda não persistido), o mesmo deve ser serializado no banco de dados através da invocação do método Insert. Um objeto somente terá um ID, após ser inserido no banco, pois a coluna código da tabela relacional do qual o objeto é serializado é do tipo auto-‐incremento. A implementação do método Merge é definida abaixo: function TEmpresaDAO.Merge(Empresa: TEmpresa): Boolean; begin if TUtil.Empty(Empresa.Codigo) then begin Result := Self.Insert(Empresa); end else Result := Self.Update(Empresa); end; Ao ser invocado o método Insert(Empresa: TEmpresa), recebe o objeto Empresa como argumento, repassado pelo método Merge; devemos serializar o mesmo para as colunas da tabela relacional do SGBD. O processo de serialização já fora comentado no artigo sobre orientação a objetos, precisamos agora realizar apenas uma pequena alteração na implementação do método Insert que é exibida abaixo:
function TEmpresaDAO.Insert(Empresa: TEmpresa): Boolean; begin Try Result := False; with Conexao.QryCRUD do begin Close; SQL.Clear; SQL.Text := 'INSERT INTO '+ TABLENAME +' (EMP_RAZAO, EMP_FANTASIA, EMP_DT_CADASTRO, EMP_DT_ABERTURA, EMP_STATUS, EMP_ALIAS, '+ ' EMP_TELEFONE, EMP_FONEFAX, EMP_CNPJ, EMP_IE, EMP_IEST, EMP_IM, EMP_CRT, EMP_CNAE, EMP_EMAIL, EMP_PAGINA, '+ ' EMP_MENSAGEM, EMP_CONTATO) '+ ' VALUES(:RAZAO, :FANTASIA, :CADASTRO, :ABERTURA, :STATUS, :ALIAS, :TELEFONE, :FONEFAX, :CNPJ, :IE, :IEST, :IM, '+ ' :CRT, :CNAE, :EMAIL, :PAGINA, :MENSAGEM, :CONTATO) '; ParamByName('RAZAO').AsString := Empresa.Razao; ParamByName('FANTASIA').AsString := Empresa.Fantasia; ParamByName('CADASTRO').AsDateTime := Empresa.DtCadastro; ParamByName('ABERTURA').AsDateTime := Empresa.DtAbertura; ParamByName('STATUS').AsInteger := Empresa.Status; ParamByName('ALIAS').AsString := Empresa.Alias; ParamByName('TELEFONE').AsString := Empresa.Telefone; ParamByName('FONEFAX').AsString := Empresa.FoneFax; ParamByName('CNPJ').AsString := Empresa.CNPJ; ParamByName('IE').AsString := Empresa.IE; ParamByName('IEST').AsString := Empresa.IEST; ParamByName('IM').AsString := Empresa.IM; ParamByName('CRT').AsInteger := Empresa.Crt; ParamByName('CNAE').AsString := Empresa.Cnae; ParamByName('EMAIL').AsString := Empresa.Email; ParamByName('PAGINA').AsString := Empresa.Pagina; ParamByName('MENSAGEM').AsString := Empresa.Observacao; ParamByName('CONTATO').AsString := Empresa.Contato; ExecSQL; end; // getCodigo para Insert Endereco Empresa.Codigo := getMaxId(); if NOT Empresa.Endereco.Insert(Empresa.Codigo, 'EMPRESA') then begin ShowMessage('erro ao inserir endereco'); // mensagem temporaria, melhorar controle de transacao // deletar registro da empresa, pois houve erro ao inserir empresa
Result := False; end; Result := True; Except on E : Exception do ShowMessage('Classe: '+ e.ClassName + chr(13) + 'Mensagem: '+ e.Message); end; end; Com a arquitetura do software logicamente separada em camadas, percebe-‐se que a manutenção na implementação do código-‐fonte em uma determinada camada não afeta diretamente a implementação contida em outra camada, confirma-‐se assim na prática que através do MVC é possível desenvolver software de qualidade com o mínimo de impacto possível, provocado pela manutenção das rotinas.
Agora que chegamos ao ponto da persistência do objeto em um SGBD, passando pelas camadas View, Controller, Model e DAO completa-‐se uma parte da seqüencia de atividades que estão claramente representadas na figura 3, através da interação do usuário com o sistema. O retorno da camada DAO dá-‐se através do método Insert(Empresa: TEmpresa): boolean que retorna true/false para a camada Model que realizou a invocação do método. Na implementação apresentada neste artigo o objeto Empresa da camada Model está sendo referenciado dentro do escopo da camada Controller que é então notificada do retorno booleano citado anteriormente e este repassa para a camada View o resultado do processamento solicitado pelo usuário, através de uma notificação visual – uma mensagem.
Foi mostrado neste artigo somente o Create (Insert) do acrônimo CRUD, as demais operações podem ser visualizadas no código-‐fonte disponibilizado em https://github.com/ryanpadilha/coding4fun.
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.
3. Conclusão
Dado o exposto, através do design pattern MVC divide-‐se logicamente a arquitetura da aplicação em quatro camadas, objetivando uma alta coesão e baixo acoplamento entre as unidade de software, porém com uma alta granularidade. Anteriormente a adoção deste padrão tínhamos uma arquitetura simples em duas camadas, porém com uma granularidade satisfatória. Quanto maior for a granularidade, mais trabalho temos ao implementar um diagrama de classes anotado utilizando a UML, como fora apresentado na figura 4. Porém a flexibilidade é transparente, poder trocar, qualquer elemento de determinada camada e manter o código de outras camadas ainda assim em perfeito funcionamento e com um certo nível de robustez é incontestável. Um exemplo disto seria a criação de várias classes dentro do pacote DAO, cada uma com acesso para um determinado banco de dados específico, como Oracle, Firebird, PostgreSQL, e assim por diante. Tornando a aplicação multi-‐banco de forma
simples. A modificação aqui é somente na camada de acesso a dados, pois a regra de negócios, formulário e controlador já estão implementados e testados.
Apesar do conceito RAD que a IDE Delphi proporciona, sem sombra de dúvidas somos capazes de aplicar design patterns e boas práticas da engenharia de software em projetos de software desenvolvidos utilizando o Delphi. Como os padrões de projetos estão documentados fica fácil a difusão deles dentro de times de desenvolvimento, sem muito esforço podemos acrescentar uma ótima qualidade no software desenvolvido, além de ter alcançado um arquitetura flexível ao ponto de ser realizado de forma menos dolorosa a manutenção da mesma.
Seria interessante a criação de um framework que aplicasse o padrão de projeto MVC sem muito esforço. Outras linguagens como o Java e C#.NET, possuem frameworks com esta finalidade, aumentando admiravelmente a produtividade do desenvolvimento de software e levando em consideração a obtenção de uma qualidade de software inquestionável. Acredito que as vantagens que o padrão MVC nos proporciona é muito maior que as desvantagens, pois o que deixa a desejar é a falta de produtividade ao implementá-‐lo.
Caso tenha alguma dúvida entre em contato. Será um prazer ajudar.
Forte Abraço a todos os leitores que acompanharam meus artigos sobre engenharia de software em geral.