Aguarde...

MVC com Java e desktop (parte 2)

Publicado em 09/07/2012

Mãos à obra!

Vamos agora desenvolver um software que implemente o MVC usando apenas a camada model, mais a frente terminaremos o software adicionando as outras camadas. Esse software será capaz de ordenar uma sequência de dígitos numéricos e salvar dois arquivos de texto, um contendo todo os passos que foram nescessários para que os números fossem ordenados e um outro com o resultado.

Primeiro, vou considerar que você já tenha o Java instalado na sua máquina e que saiba se virar com alguma IDE ou utilize um editor de texto comum (se você for guerreiro!), eu vou utilizar o NetBeans.

Para começarmos, crie um projeto Java-SE (Standard Edition, aplicação desktop). No meu caso o projeto se chama 'MVC'. Em seguinda crie uma estrutura de pastas com os seguintes nomes:

  • br.com.gqferreira
  • br.com.gqferreira.controller
  • br.com.gqferreira.model
    • br.com.gqferreira.model.bo
    • br.com.gqferreira.model.dao
    • br.com.gqferreira.model.vo
  • br.com.gqferreira.controller
  • br.com.gqferreira.view

Por padrão a estrutura de pastas são nomeadas com o domínio da empresa ou do desenvolvedor de forma invertida (br.com.gqferreira). Se você ainda não tem um domínio, invente! Coloque o seu nome ou o do projeto com diretório principal.

Se você criou os diretórios através de uma IDE, irá notar que na prática foi criado mais diretórios do que parecem. Se você navegar pelas pastas fora da IDE, irá ver que ficou assim:

Estrutura de pastas MVC

Estrutura de pastas MVC

Isso é o correto, se a sua estrutura não ficou assim, tem algo errado.

Implementando a VO

Vamos utilizar duas classes desse tipo, uma chamada Passos que conterá as informações relacionadas à uma verificação pelo método de ordenação. A outra classe se chama Ordenação e contém os dados relativo ao resultado final do processo. Se não entendeu muito ainda, não tem problema, quando estiver implementado e funcionando você endentenderá.

                        package br.com.gqferreira.model.vo;

                        /**
                         *
                         * @author Gustavo Ferreira
                         * @see Classe que armazenas os dados relativo a cada passo da ordenacao
                         */
                        public class Passos {

                            private String numeroAnterior;
                            private String numeroResultante;
                            private String descricao;

                            /**
                             * Construtor que preenche todos os atributos do objeto
                             * @param numeroAnterior
                             * @param numeroResultante
                             * @param descricao 
                             */
                            public Passos(String numeroAnterior, String numeroResultante, String descricao) {
                                this.numeroAnterior = numeroAnterior;
                                this.numeroResultante = numeroResultante;
                                this.descricao = descricao;
                            }

                            //Get and Set
                            public String getDescricao() {
                                return descricao;
                            }

                            public void setDescricao(String descricao) {
                                this.descricao = descricao;
                            }

                            public String getNumeroAnterior() {
                                return numeroAnterior;
                            }

                            public void setNumeroAnterior(String numeroAnterior) {
                                this.numeroAnterior = numeroAnterior;
                            }

                            public String getNumeroResultante() {
                                return numeroResultante;
                            }

                            public void setNumeroResultante(String numeroResultante) {
                                this.numeroResultante = numeroResultante;
                            }

                            //Sobrescrevendo o toString do objeto
                            @Override
                            public String toString() {
                                if (this.getNumeroAnterior() == null) {
                                    return "\nDescricao: ".concat(this.descricao);
                                } else {
                                    return this.getNumeroAnterior().concat(" >> ").concat(this.getNumeroResultante()).concat("\nDescricao: ").concat(this.descricao).concat("\n\n");
                                }
                            }
                        }
                    

Segue a baixo a outra classe de valor

                        package br.com.gqferreira.model.vo;

                        /**
                         *
                         * @author Gustavo Ferreira
                         * @see Classe que cria objeto capaz de armazenar os dados relativo ao processo
                         * de ordenacao
                         */
                        public class Ordenacao {

                            private int numeroOriginal;
                            private String numeroOrdenado;
                            private int qtdeTrocas;

                            /**
                             * Construtor padrao que insere em todos os atributos do objeto
                             * @param numeroOriginal
                             * @param numeroOrdenado
                             * @param qtdeTrocas 
                             */
                            public Ordenacao(int numeroOriginal, String numeroOrdenado, int qtdeTrocas) {
                                this.numeroOriginal = numeroOriginal;
                                this.numeroOrdenado = numeroOrdenado;
                                this.qtdeTrocas = qtdeTrocas;
                            }

                            //Get and Set
                            public String getNumeroOrdenado() {
                                return numeroOrdenado;
                            }

                            public void setNumeroOrdenado(String numeroOrdenado) {
                                this.numeroOrdenado = numeroOrdenado;
                            }

                            public int getNumeroOriginal() {
                                return numeroOriginal;
                            }

                            public void setNumeroOriginal(int numeroOriginal) {
                                this.numeroOriginal = numeroOriginal;
                            }

                            public int getQtdeTrocas() {
                                return qtdeTrocas;
                            }

                            public void setQtdeTrocas(int qtdeTrocas) {
                                this.qtdeTrocas = qtdeTrocas;
                            }

                            //Sobrescrevendo o toString do objeto
                            @Override
                            public String toString() {
                                return 
                                        String.valueOf(this.numeroOriginal)
                                        .concat(" virou:\n")
                                        .concat(String.valueOf(this.numeroOrdenado))
                                        .concat("\nQtde de trocas: ")
                                        .concat(String.valueOf(this.qtdeTrocas));
                            }
                        }
                    

Implementando a DAO

Também teremos duas classes DAO's, pois o sistema manipulará dois arquivos de texto. Se o sistema manipulasse dez arquivos, seria dez classes, uma para cada arquivo com os métodos que forem nescessários (excluir, salvar, editar, atualizar, etc.). A mesma regra valeria se fosse para manipular tabelas do banco de dados, uma DAO para cada tabela.

                        package br.com.gqferreira.model.dao;

                        import br.com.gqferreira.model.vo.Passos;
                        import java.io.FileNotFoundException;
                        import java.io.PrintWriter;
                        import java.util.List;

                        /**
                         *
                         * @author Gustavo Ferreira
                         * @see Classe que executa as operacoes de IO (entrada e saida) do sistema com relacao
                         * aos dados resultantes do passo a passo
                         */
                        public class PassosDAO {

                            /**
                             * Metodo que recebe todos os passos (lista) e salva todos em um arquivo
                             * @param passos
                             * @throws FileNotFoundException 
                             */
                            public void salvarPassos(List passos) throws FileNotFoundException{
                                PrintWriter pw = new PrintWriter("passos.txt");
                                for (Passos p : passos){
                                    pw.print(p);   
                                }
                                pw.flush();
                                pw.close();
                            }
                        }
                    

Segue abaixo a outra classe DAO

                        package br.com.gqferreira.model.dao;

                        import br.com.gqferreira.model.vo.Ordenacao;
                        import java.io.FileNotFoundException;
                        import java.io.PrintWriter;

                        /**
                         *
                         * @author Gustavo Ferreira
                         * @see Classe que executa as operacoes de IO (entrada e saida) do sistema com relacao
                         * aos dados resultantes da ordenacao
                         */
                        public class OrdenacaoDAO {

                            /**
                             * Metodo que salva em um arquivo de texto os dados do objeto de ordenacao
                             * @param Ordenacao ordenacao
                             * @throws FileNotFoundException 
                             */
                            public void salvar(Ordenacao ordenacao) throws FileNotFoundException{
                                PrintWriter pw = new PrintWriter("ordenacao.txt");
                                pw.print(ordenacao);
                                pw.flush();
                                pw.close();
                            }
                        }
                    

Implementando a BO

Nosso sistema terá apenas uma BO com apenas um método, o de ordenação pelo algoritmo "Bolha". Sistemas comerciais geralmente tem várias BO's, uma para cada responsabilidade. Por exemplo, essa BO que vamos criar é responsável por ordenar números e poderia conter vários métodos, cada um utilizando um algoritmo diferente de ordenação.

Todo acesso à classes DAO's é feito através das classes BO's. É como se a BO dissesse:
"Ninguém vai à DAO senão por mim!"

Imagine que você tem um sistema com três DAO's, cada uma acessa uma tabela do banco de dados. A sua aplicação precisa inserir dados em uma tabela e logo em seguida inserir nas outras duas mas se der erro na inserção da última tabela, a ação toda precisa ser desfeita para garantir que "Ou insere em todas ou em nenhuma". Para assegurar isso, será preciso usar transações que deverão ser manipuladas pela BO. Ou seja, a BO solicita as ações para as três DAO's e ela mesmo gerencia para poder desfazer se for nescessário. Por isso sempre chame DAO's a partir da BO!

A BO a seguir pode confundir bastante, principalmente se você ficar batendo cabeça para entender como funciona a lógica de ordenação. Se isso acontecer, pense o seguinte: É uma classe que contém um metodo responsável em receber um número (532135), ordena-lo (123355) e em seguida solicitar às duas DAO's que salvem o passo-a-passo e o resultado, só isso.

                        package br.com.gqferreira.model.bo;

                        import br.com.gqferreira.model.dao.OrdenacaoDAO;
                        import br.com.gqferreira.model.dao.PassosDAO;
                        import br.com.gqferreira.model.vo.Ordenacao;
                        import br.com.gqferreira.model.vo.Passos;
                        import java.util.ArrayList;
                        import java.util.List;

                        /**
                         *
                         * @author Gustavo Ferreira
                         * @see Classe que contem o(s) metodo(s) de ordenacao e eh capaz de processa-los
                         */
                        public class OrdenacaoBO {

                            /**
                             * Metodo responsavel em fazer a ordenacao pelo algorito BubbleSort (metodo bolha)
                             * @param Int numero
                             * @return Ordenacao
                             */
                            public Ordenacao bubbleSort(int numero) {
                                try {
                                    //Transforma em String para fazer as trocas consideranco caracter por caracter
                                    //Converto o numero do tipo Int para String e depois gero um array de chars.
                                    char[] digitos = String.valueOf(numero).toCharArray();
                                    //Nosso 'balde' intermediario entre as trocas, variavel auxiliar.
                                    char aux;
                                    //Outra auxiliar que serve para armazenar o numero antes da modificacao para
                                    //se criar o 'Passo'
                                    char[] antes;
                                    //Variavel que sera incrementada a cada troca para contar quantas trocas houve
                                    int qtdeTrocas = 0;
                                    //Vetor de passos para descrever todo o processo
                                    List passos = new ArrayList();
                                    //Variavel que marca determina se houve trocas, usada para
                                    //interromper o processo quando ja nao houver mais numeros a serem
                                    //ordenados
                                    boolean continua=true;

                                    //Sera percorrido todos os numeros de acordo com o tamanho da sequencia
                                    for (int i = 0; i < digitos.length; i++) {
                                        if (!continua){ //Verificando se foram feitas trocas no ultimo ciclo, se nao foram, indica que ja esta ordenado
                                            break; //Interrompe o algoritmo    
                                        }
                                        //Descrevendo o passo
                                        passos.add(new Passos(null, null, "Inicio da verificacao numero ".concat(String.valueOf(i)).concat("\n------------\n")));
                                        continua=false;
                                        //Percorrendo cada numero com o seu proximo
                                        for (int j = 0; j < digitos.length - 1; j++) {
                                            if (digitos[j] > digitos[j + 1]) {
                                                //Esse numero eh maior que o proximo, troca!
                                                antes = new String(digitos).toCharArray();
                                                aux = digitos[j];
                                                digitos[j] = digitos[j + 1];
                                                digitos[j + 1] = aux;
                                                //Incrementando a quantidade de trocas
                                                qtdeTrocas++;
                                                //Descrevendo o passo                    
                                                passos.add(new Passos(new String(antes), new String(digitos), "Trocou-se o digito ".concat(String.valueOf(digitos[j+1])).concat(" pelo ").concat(String.valueOf(digitos[j]))));
                                                continua=true;
                                            } else {
                                                passos.add(new Passos(new String(digitos), new String(digitos), "Nao houve troca pois o numero ".concat(String.valueOf(digitos[j])).concat(" ja eh menor/igual que ").concat(String.valueOf(digitos[j + 1]))));
                                            }
                                        }
                                    }

                                    //Persiste os resultados
                                    Ordenacao ordenacao = new Ordenacao(numero, new String(digitos), qtdeTrocas);
                                    new OrdenacaoDAO().salvar(ordenacao);
                                    new PassosDAO().salvarPassos(passos);
                                    //Retorno um objeto da classe Ordenacao informando os resultados.
                                    return ordenacao;

                                } catch (Exception ex) {
                                    ex.printStackTrace();
                                    return  null;
                                }
                            }
                        }
                    

Pronto, sua aplicação já estará funcionando, mesmo que sem nenhuma interação com o usuário e nem interface gráfica, essa é a essência da cama model, ser o motor da aplicação!

Testando a model

Vamos testar nossa aplicação agora, para isso coloque a seguinte classe no pacote principal da aplicação (br.com.gqferreira):

                        package br.com.gqferreira;

                        import br.com.gqferreira.model.bo.OrdenacaoBO;
                        import br.com.gqferreira.model.vo.Ordenacao;

                        /**
                         *
                         * @author Gustavo Ferreira
                         */
                        public class MVC {

                            public static void main(String[] args) {
                                Ordenacao ordenacao = new OrdenacaoBO().bubbleSort(532135);
                                System.out.println(ordenacao); //Invoco o toString() da classe Ordenacao
                            }
                        }
                    

Essa classe contém o método main que é o método principal e será nescessário daqui pra frente.
Agora você já pode testar o sistema, embora não tenha ainda interface gráfica. Para interagir com a camada model, será necessário fazer alterações no código do método main.

Licença Creative Commons
MVC com Java e desktop (parte 2) de Gustavo Ferreira é licenciado sob uma Licença Creative Commons Atribuição-CompartilhaIgual 3.0 Não Adaptada.

Comentarios (26) para MVC com Java e desktop (parte 2)

Augusto Nunes,   09/05/2013

Prezado Gustavo,

Parabéns pelo artigo! Você conseguiu explicar de forma simples, clara e objetiva o que eu vinha procurando entender a muito tempo e com a exemplificação ficou 100% !!!

Abraços,

Augusto

Gustavo Ferreira,   09/05/2013

www.gqferreira.com.br

Obrigado, fico feliz que tenha gostado. Sinta-se a vontade para continuar navegando pelos artigos.

Agraços,
Gustavo Ferreira.

Augusto Nunes,   28/05/2013

Bom Dia, Gustavo. Tenho a seguinte dúvida, e gostaria da sua opinião/visão sobre a mesma:

- Estou desenvolvendo um sistema onde as classes envolvidas não são "Pessoas", "Pedidos", "Itens_Pedido", etc., mas sim "Editor de Textos", "Player de Vídeo", "Player de Áudio" e "Área de Exibição de Imagens". Este sistema deve permitir aos atores gerar seus próprios textos, simultaneamente gravar / reproduzir áudio / vídeo, etc...

Como seria a organização dessas classes em na arquitetura MVC? Quem seriam as classes BO, VO e Controller?

Desculpe-me pela falta de conhecimento, mas deveria eu pensar que este é o desenvolvimento de um "Framework" ???

Agradecendo qualquer esclarecimento,

atenciosamente

Augusto Cesar Nunes

Gustavo Ferreira,   30/05/2013

www.gqferreira.com.br

Nesse caso eu imagino que "Player de Vídeo", "Editor de Texto" entre outros possam ser módulos independente, cada um com sua estrutura MVC. Se os módulos precisarem trabalhar em conjunto uns com os outros, você precisará que todos tenham uma ligação com a mesma controller para que as ações de um módulo interfira no outro.

Ou (melhor)...

Não precisam ser módulos independentes. Você pode ter uma DAO para audio (grava e lê), outra para video (grava e lê), uma para texto (grava e lê) e outra para imagens (grava e lê). Uma BO que centraliza os acessos à DAO e que resolva algum processo (conversao de formatos de audio e/ou video, redimensionamento de imagens, efeitos, restrição de acessos, login, logs, etc). Você poderá ter uma view para cada parte do sistema e uma que centralize. Olhe nessa imagem o que imaginei.









As VOs podem ser classes separadas para cada tipo de arquivo a ser salvo, como por exemplo: Audio.java contendo nome, duração, titulo, bytes[], formato, etc...

Carlos Soares,   20/08/2013

Muito bom mesmo, agora ficou muito mais claro.
Valew Gustavo.

Misael C. Homem,   10/10/2013

Boa tarde Gustavo,

Parabéns pelo artigo. Só estou com uma dúvida do projeto todo, e se puder sanar agradeço.
Sabe-se que na camada model se insere as classes do modelo, e que no caso as classes tipo VO apenas tratam de valores. Ate aqui blz, a questão é se na nas classes de BO contém as regras de negócio então o trecho:

public String getNome() {
return "Sr. "+nome;
}

A regra de prefixar o valor "Sr. " antes do nome não deveria ser posta dentro de uma BO ?
Eu entendi o por que tu colocou este exemplo, obviamente para explicar o conceito get e set, mas num cenário real o mais correto seria ter essa regra em uma classe do tipo BO, correto ?

Fico no aguardo.

Misael

Misael C. Homem,   10/10/2013

Gustavo,

Tenho mais 3 questão relevantes:

1) Em um sistema em que se aplique o MVC, onde (qual o nome da pasta) ficariam arquivos de recurso da aplicação tais como ícones, figuras, imagens, sons, etc ?
OBS: em uma app que tenho aqui de modelo eu criei uma pasta chamada resource para comportar tais arquivos!

2) Na web e até em alguns livros, os arquivos do tipo .jar oriundos de classes de conexão por exemplo MySql em geral são postos em uma pasta chamada lib para serem referenciados pela aplicção no caso inclusos como jar, utilizando o MVC isto estaria correto ou se deveria por em uma pasta com nomeação diferente?

3) Poderia alterar o teu modelo incluindo estas questões ?

Gustavo,   16/10/2013

www.gqferreira.com.br

Respondendo a primeira dúvida...

A VO é uma classe que também está no pacote model, portanto também possui alguns comportamentos de negócio. E como é uma classe que visa encapsular os atributos, deixar essa regra do "Sr" fora pode por abaixo o encapsulamento. Imagine que a classe Pessoa possa ser instanciada em várias métodos de uma BO, você vai precisar fazer o tratamento do "Sr" idêntico em todos os pontos. Isso acabaria aniquilando também o objetivo dos métodos controladores de acesso (get e set).

As classes do tipo BO devem se preocupar mais com o "algoritmo" da coisa, a engine. Mas você pode criar dois get's, um com tratamento e outro sem, assim, quando precisar alterar o tratamento, todo mundo que utilize esse get será afetado, deixando de afetar aos métodos que optarem por utilizar o get 'puro'

Gustavo,   16/10/2013

www.gqferreira.com.br

1) Por entender que tais recursos fazem parte da view, eu crio uma pasta de resources dentro do pacote view:
br.com.gqferreira.projectname.view.resources.[images|sounds|videos|icons]

2) Não precisa se preocupar em colocar as bibliotecas na estrutura MVC do teu projeto, até porque internamente cada biblioteca deve possuir a sua própria estrutura, o que dificultaria na hora de julgar se é model, se é controller... se é a DAO da model ou se... enfim, a biblioteca provavelmente é mista. Coloque em uma pasta chamada lib mesmo.

3) O que você sugere que possa ser alterado no modelo?

Obrigado pela atenção,
Gustavo Ferreira.

Diego,   06/03/2015

Boa tarde Gustavo,

Gostei muito desse artigo ,
muito bem explicado e consegui entender de primeira rs ..

sou iniciante e ajudou muito para poder projetar meus projetos
vlw cara ..

Gustavo,   06/03/2015

www.gqferreira.com.br

Aqui ainda é bom dia Diego rsrs..

Que bom que você tenha achado útil o artigo, fico contente.

Abraços!

Cid Reis,   12/04/2015

Parabéns pelo o artigo tirou varias duvidas porem surgiu outras rsrsrs

no padrao de desenvolvimento web notei em varios tutorias e artigos que muda bastante esses padroes onde entrar como exemplo:

Util
Filter

vilson,   20/05/2015

E o controller precisa conhecer as duas interfaces!? e se uma terceira, quarta , quinta forem adicionadas?

Yuri Caldas,   28/07/2015

Olá Gustavo, gostei bastante do seu artigo.

Olhá só, estou com um problema, criei 2 classes controller, 1 para controlar um login e outra pra controlar a tela de configuração de conexão com o Banco de Dados.

Quando executo o sistema, a tela de login se abre, aí eu clico no botão para abrir a tela de configuração.

Ao abrir a tela de configuração, quando vou sair dela, dou um dispose() e instancio uma nova classe da controller do login.

O problema é, quando clico em fechar ou pra sair da nova tela de login, a tela somente some, mas o sistema continua em execução.

Como posso resolver isso? Tem alguma ideia?

Obrigado

CARLOS HENRIQUE COSTA CAPISTRANO,   06/10/2015

Parabéns muito bom.

Memoriacache,   07/10/2015

www.memoriacache.com.br

Para quem se interessar, aqui tem outra aplição desktop com netbeans: http://www.memoriacache.com.br/aplicacoes-desktop-com-netbeans-swing/

Sidnei Favoreto Vieira,   22/10/2015

Olá Gustavo.

Parabéns pelo artigo, esclareceu várias duvidas, mas ainda ficaram algumas.

Por um acaso não teria um exemplo de sistema que grave em banco e valide campos da tela para ter uma ideia não né?

Hoje eu tenho um sistema com uma tela padrão que valida de forma automática os campos, e as minhas telas de cadastro eu herdo ela.

Queria saber como fazer isso usando MVC.

Acredito que eu tendo um exemplo de um sistema que faça algumas validações e grave em banco eu consigo fazer o meu.

Obrigado.

Att,
Sidnei

comentario,   16/01/2016

Muito boa a didática, direta ao ponto e demonstra de forma muito clara. parabéns.

Sidney Francisco,   15/03/2016

Artigo excelente!! Já pesquisei bastante para obter um exemplo de MVC. De todos que venho pesquisando, considerei esse, o melhor!! Obrigado!! Um abraço!

Monique,   02/06/2016

Me ajudou muito. Ótimo artigo, parabéns!

Gabriel Paiva,   10/08/2016

Otima explicação. Clareou muito para mim.

Olá Gustavo.

Muito obrigado por este artigo, estou começando agora com o MVC, e com esta primeira parte do seu artigo estou apreciando a utilidade deste padrão de projeto.

Custódio,   28/11/2016

Muito bom e agradeço pela explicação, procurei bastante como fazer MVC e final achei e bem explícito

Junior,   19/12/2016

Parabéns. Me ajudou muito, o assunto abordado foi muito bem explicado e sanou muitas dúvidas que tinha. Muito obrigado, um grande abraço e sucesso.

Mayara Alves,   13/01/2017

Parabéns pelo artigo, depois de pesquisar muito finalmente encontrei um artigo que me fez entender claramente sobre o assunto, ótima didática. Muito obrigada!

Michel Souza,   10/02/2018

Boa tarde Gustavo Ferreira, gostaria de poder utilizar esse artigo em um documento em PDF totalmente didático, sem fins lucrativos que estou desenvolvendo, para ajudar iniciantes assim como eu na programação em Java, utilizando o modelo MVC. Não consegui encontrar em pesquisas explicações tão boas, assim como as suas, se poderei utilizar, deixarei respectivamente a fonte do seu site e uma parte dizendo que o Artigo é de sua autoria e dos demais autores com suas respectivas fontes. Obrigado pela atenção!

Gostou do artigo? Acha que poderia ser melhor em algum ponto? Dê o seu feedback!