segunda-feira, 24 de julho de 2017

O princípio da responsabilidade única - S.O.L.I.D

Olá ! Meu nome é João Paulo Maida e bem-vindos ao blog Preciso Estudar Sempre.

Pare por um instante e reflita sobre a seguinte frase:


Uma classe deve ter somente um único motivo para mudar. (MARTIN, ROBERT; MARTIN MICAH)


Este é o princípio da responsabilidade única (Single-Responsibility Principle - SRP em inglês) e se você for um iniciante no mundo da programação e achar esta declaração estranha, não se espante. Este conceito não é banal e tampouco é ensinado na faculdade, logo sua reação é normal. Na verdade, são poucos os programadores que sabem do que estou falando e isto de fato é preocupante, visto que o conceito que iremos discutir aqui hoje forma um dos pilares do que realmente a orientação a objetos trata.

Voltando à frase, conseguiu pensar ? Conseguiu ver a quantidade de conhecimento preso em uma única frase ? Não !? Bem, minha meta é abrir sua mente para o verdadeiro desafio de se construir software orientado a objetos.

Análise a situação abaixo.
Figura 1 - Modelo de classes acoplado

A classe AbridorDeGarrafas tem como responsabilidade abrir garrafas, sejam elas com tampa metálica, usadas nas garrafas de cerveja, ou com rolha de cortiça, usadas nas garrafas de vinho. Os objetos Taverna e Adega dependem destes métodos para funcionar. Queremos realizar uma modificação na implementação do método de abrir garrafas com tampa metálica sem causar impacto no funcionamento do método que abre garrafas com rolha. Será que isto é possível ? Não é preciso muito tempo para ver que a resposta é não porque será necessário recompilar, retestar e reimplantar todo este modelo para que esta mudança não resulte em problemas posteriores.

Então como alterar algo sem mexer no que já funciona ? Lembre-se que hoje somente o objeto Adega depende de AbridorDeGarrafas, mas amanhã pode existir uma gama de objetos com esta mesma dependência. Então, o que fazer ?

Isto tudo acontece por um único motivo, a classe AbridorDeGarrafas possui mais de 1 responsabilidade e tal nos leva a um modelo altamente acoplado, onde as mudanças feitas em um método afetam o funcionamento do outro. Isso é péssimo e não pode continuar 👎. Tudo isto fere o princípio da responsabilidade única, então a pergunta que nos sobra é: como corrigir o problema ?

Simples ! Devemos separar as responsabilidades da classe AbridorDeGarrafas. Entenda como responsabilidade uma razão para mudar e aqui existem duas para isso, são elas: atender solicitações de abertura de garrafas com tampa metálica e atender solicitações de abertura de garrafas com rolha. Entender essa separação é crucial para a montagem de um design que é ao mesmo tempo robusto e flexível, mas separar não significa que uma classe só deve conter um único método. Ao contrário, ela pode conter quantos métodos quiser desde que todos estejam relacionados a mesma responsabilidade. A partir do momento em que identificamos uma nova responsabilidade, a separação deve acontecer. Eis como o nosso modelo fica.
Figura 2 - Modelo de classes altamente coeso

Agora temos duas classes, uma que só abre garrafas com tampa metálica e outra que só abre garrafas com rolha e cada uma possui um método chamado abrirGarrafa(). Não há mais necessidade de especificar no nome do método que aquela abertura de garrafa é com tampa metálica ou de rolha, pois o nome da classe já tem essa expressividade. É dedutível que o abrirGarrafa() da classe AbridorDeGarrafasComTampaMetalica só abre garrafas com tampa metalica. Dessa forma podemos realizar quais mudanças quisermos sem afetar o que já funciona e isto é ótimo 👍.

Contudo, ainda falta um ponto para se pensar. Sempre que identificarmos mais de uma responsabilidade ela deve ser separada ? Levantar essa dúvida a esta altura pode parecer antagônico, mas não é. Este princípio é um dos mais difíceis e todos os outros da plataforma S.O.L.I.D se remetem à ele, logo esta indagação faz sentido e para respondê-la devemos levar em conta o contexto. Se uma alteração implica em mudar o todo não há necessidade de separar responsabilidades, pois para aquele contexto aquilo consiste uma única responsabilidade. Profundo, não !? 😕

Considere o seguinte exemplo: suponha que ao invés da mudança ser na abertura de garrafas com tampas metálicas, ela seria a adição de um mecanismo que melhora o encaixe do abridor na garrafa. Realmente deveríamos separar as responsabilidades aqui visto que a alteração afeta o todo ? A resposta é não, logo não faz sentido. Não existe uma fórmula mágica para todas as situações, cada uma deve ser avaliada separadamente.

Chegamos ao fim de mais um post. Nas próximas publicações veremos mais princípios da plataforma S.O.L.I.D e como eles se complementam. Como já é de costume, se inscreva no blog e no canal do youtube para ficar por dentro das novidades, indique para seus amigos e compartilhe em suas redes sociais.

Até a próxima minha boa gente ! 😘

Leia nossa postagem anterior: O algoritmo de Dijkstra - Grafos

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, envie um e-mail ou uma mensagem na nossa página do Facebook.

E-mail: precisoestudarsempre@gmail.com
Facebook: https://www.facebook.com/precisoestudarsempre/
Canal Preciso Estudar Sempre: https://www.youtube.com/channel/UCUoW8dS38rXr0a5jWU57etA

Referências

MARTIN, ROBERT; MARTIN MICAH; Agile Principles, Patterns and Practices in C#; 2006
Leia Mais ››

sexta-feira, 14 de julho de 2017

O algoritmo de Dijkstra - Grafos

Olá ! Meu nome é João Paulo Maida e bem-vindos ao blog Preciso Estudar Sempre.

Você já pensou em alguma vez fazer uma daquelas viagens como mochileiro que você sai sem rumo de casa, só com mochila e dinheiro para a passagem do ônibus ? Seu destino final você decide a cada parada. Hoje você pode estar saindo do Rio de Janeiro e amanhã estar amanhecendo em alguma cidade do interior de Minas Gerais. A partir de lá você pode decidir ir para São Paulo, e de lá para o Mato Grosso. Em cada cidade que você pára, avalia quais podem ser seus destinos e quais são os mais baratos. Até porque para este tipo de viagem a grana é sempre curta.

Devo admitir que este tipo de aventura sempre foi um sonho para mim, mas acho que dificilmente um dia irei realizá-lo. Se você um dia já se aventurou de tal forma ou pensa em se aventurar, sabe que seria interessante ter uma relação das conexões das cidades baseadas em seus custos de transporte onde esses são baseados em suas distâncias, exemplo: ir do RJ para SP custa R$ 80, do RJ para MG custa R$60, de MG para MT custa R$30 e por aí vai. Com essa relação em mãos fica fácil decidir qual caminho tomar gastando pouco com passagem. Seria um sonho, não ?

Para nossa alegria e felicidade, este sonho sim é possível atingir. Mais uma vez a teoria dos grafos chega para salvar o dia com um algoritmo que nos dá exatamente a resposta que estamos procurando. Apresento o algoritmo de Dijkstra. A criação de Edsger Dijkstra é baseada na representação da matriz de adjacência de um grafo e não somente encontra o caminho mais curto a partir de um nó especificado até outro, como também os caminhos mais curtos do mesmo nó especificado até os outros (LAFORE, ROBERT).

Imaginemos a nossa viagem aventureira representada pela Figura 1 e não se prenda a detalhes de geografia.
Figura 1 - Mapa das cidades

Você está partindo do Rio de Janeiro e quer saber quais são os caminhos mais baratos para as próximas cidades, para assim chegar ao seu destino final pelo melhor caminho. Você sabe que em cada cidade que pára existe um agente da empresa de ônibus e que ele pode te informar os trajetos e tarifas. Então, você pega seu bloquinho (todo bom viajante deve ter um) e faz uma tabelinha com essa relação para que no final você tenha todas as informações “mastigadas”.
Tabela 1 - Tabela das viagens mais baratas
Então, assim como eu, você é um amante da teoria dos grafos e pensa: as cidades podem ser os vértices de um grafo e os trajetos entre elas as arestas. Eu sei que cada trajeto tem uma direção e um preço de passagem, então isso pode ser respectivamente a direção e o peso das arestas. No fim das contas, eu tenho um grafo ponderado orientado. Isso é ótimo !

É aí que entra o algoritmo de Dijkstra. Ele pega o seu grafo, analisa, e como resultado gera uma árvore. Esta árvore segue os conceitos básicos de uma árvore mas não é como uma árvore geradora mínima, a qual já vimos (clique aqui). Ela é diferente porque é montada levando em conta o somatório dos pesos a partir do nó inicial.

Abaixo estão relacionadas as etapas do algoritmo. Os detalhes de cada uma serão vistos mais à frente. Com o entendimento dessas etapas, entender o algoritmo em Java fica muito mais fácil.

Etapas do algoritmo:
  1. Lembra da tabelinha da relação dos caminhos mais baratos ? Aqui ela toma a forma de um array, onde seu tamanho reflete a quantidade de vértices do grafo, cada posição representa um vértice e inicialmente é preenchido por completo com um valor muito grande (veremos o motivo disso mais à frente) que chamaremos de INFINITY. Chamaremos este array de array de menor caminho.
  2. Após montado, o nó inicial, ou seja, nosso ponto de partida é analisado. São adicionados no array de menor caminho somente os vértices que são adjacentes ao nó inicial. Essa adição tem a forma de um objeto com informações sobre o vértice antecessor ao vértice analisado e o custo para chegar até ele. É necessário ter informações sobre o antecessor pois precisamos saber como chegamos ali (entenderemos esse ponto melhor a frente). Porque é assumido que estes vértices representam os melhores caminhos a partir do vértice inicial e podem ser inseridos no array de menor caminho ? A resposta é óbvia. Se os nós que são inseridos no array são aqueles adjacentes ao nó inicial, logo não existe nada que separe-os e assim se tornam os melhores custos neste trajeto. É importante citar que essas adições são feitas por cima dos valores que já existem em uma determinada posição do array.
  3. Lembra que o resultado do algoritmo de Dijkstra é uma árvore ? Então, já podemos adicionar o primeiro nó (início) na árvore.
  4. A partir dos nós restantes, verificamos 1 por 1 para averiguar qual tem o menor custo a partir do início.
  5. Caso não haja nenhum é porque o nó inicial não tem conexões, ou seja, é inatingível ou todos já estão na árvore. Caso contrário, o nó com menor custo (adjacente ao inicial neste caso) é selecionado.
  6. Uma vez selecionado, é adicionado na árvore e o array de menores caminhos é atualizado.
  7. A atualização tem forma parecida com a adição citada no passo 2, ou seja, a mesma estrutura de objeto é citado. Consiste em analisar se o somatório de valores do início até o nó selecionado é menor que o valor já registrado. Isto deve ser feito por causa do passo 2.
  8. Após esses passos terminados, o array de menor caminho está finalizado.
Após a aplicação do algoritmo, nossa tabela deverá ficar assim:
Tabela 2 -Tabela de viagens mais baratas preenchida

Através da Tabela 2 fica claro porque precisamos da informação do antecessor ? Com ela em mãos conseguimos refazer todo o caminho feito desde do nó inicial, ou seja, agora eu sei que o melhor caminho para a cidade de Bonito no estado do Mato Grosso do Sul é através de Ribeirão Preto em São Paulo e que o melhor caminho para este é via São Paulo capital, e para este o melhor é caminho é vir diretamente de Rio de Janeiro capital. Tudo mais claro agora não !?

Agora veremos a implementação desse algoritmo em Java e como já é de costume, é necessário dizer que é possível implementar este algoritmo em qualquer linguagem de programação. A linguagem Java foi escolhida somente por uma questão de afinidade.

O código abaixo é um dos mais difíceis já mostrados aqui no blog, mas não se espante que com calma tudo fica fácil. Não mostrarei o código inteiro aqui porque tal tornará a leitura complicada, visto que o que realmente importa são as três funções chave as quais compõem o algoritmo de Dijkstra.

findAllShortestPaths()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void findAllShortestPaths(){
    int startTree = 0;
    vertexList[startTree].setIsInTree(true);                                //adiciono o primeiro vértice do grafo na árvore
    nTree = 1;                                                                //mudo o contador porque acabei de adicionar um nó na árvore

    for (int i=0; i<nVerts; i++) {                                            //inicializo o array de menores caminhos com as distâncias
        int distance = adjMat[startTree][i];                                //das adjacências do primeiro vértice
        shortestPathArray[i] = new ShortestPathObject(startTree, distance);
    }

    while (nTree < nVerts) {                                                        //até todos os nós estarem na árvore
        int shortestVertex = getMin();                                                //seleciona o vértice que tem o valor mínimo, essa função provê
        int shortestDistance = shortestPathArray[shortestVertex].getDistance();        //a direção que o algoritmo vai seguir

        if(shortestDistance == INFINITY){                                            //se todos forem infinito ou todos na árvore
            System.out.println("There are unreachable vertices");
            break;
        } else {
            currentVert = shortestVertex;
            startToCurrent = shortestDistance;
        }

        vertexList[currentVert].setIsInTree(true);                                    //coloca o vértice atual na árvore
        nTree++;                                                                    //mais um nó entrou na árvore
        updateShortestPathArray();                                                    //atualizo os valores mínimos
    }

    displayPaths();                                                                    //exibo todos os nós

    nTree = 0;                                                                        //limpa a ávore
    for (int i=0; i<nVerts; i++) {
        vertexList[i].setIsInTree(false);
    }        
}

Aqui é onde acontece toda a mágica. Como existem muitos detalhes que devem ser vistos, esta função divide essa responsabilidade com duas outras funções: getMin() e updateShortestPathArray(). Tentei deixar o código o mais explicado possível através de comentários, nomes de variáveis e métodos. Por causa disso não me estenderei em explicações aqui.

getMin() e updateShortestPathArray()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private int getMin(){
    int shortestDistance = INFINITY;
    int shortestVertex = 0;

    for (int i=1; i<nVerts; i++) {                                                                    //passo por todos os nós
        if (!vertexList[i].isInTree() && shortestPathArray[i].getDistance() < shortestDistance) {    //analiso se o nó já está na árvore e se a distância dele
            shortestDistance = shortestPathArray[i].getDistance();                                    //é a menor. Se for, atualizo as variáveis
            shortestVertex = i;
        }
    }
    return shortestVertex;
}

private void updateShortestPathArray(){
    int column = 1;
    while (column < nVerts) {                                                        //percorre todas as colunas da "tabelinha"
        if (vertexList[column].isInTree()) {                                        //se a coluna da "tabelinha" já estiver na árvore não há
            column++;                                                                //de atualizar sua entrada no array de valores mínimos
            continue;
        }

        int currentToTarget = adjMat[currentVert][column];                            //variável que guarda o valor da distância do nó atual(RJ) até um nó de destino que não está na árvore, nem sempre esse vértice de destino tem uma conexão de fato
        int startToTarget = startToCurrent + currentToTarget;                        //variável que guarda a distância total do início até o destino
        int shortestDistance = shortestPathArray[column].getDistance();                //recupera o valor do array de valores mínimos

        if (startToTarget < shortestDistance) {                                        //essa comparação é essencial pois se não há uma conexão de fato entre dois vértices, a soma total será INFINITY + algum valor de aresta e este é menor que somente INFINITY, logo não há necessidade de atualização nesse caso.
            shortestPathArray[column].setParentVert(currentVert);                    //caso a soma seja de valores que realmente possuem conexão (RJ-SP-RP-BN) ela será menor que INFINITY e logo há porque atualizar
            shortestPathArray[column].setDistance(startToTarget);
        }
        column++;
    }
}

Entender o que essas funções fazem é essencial para um entendimento total da implementação do algoritmo. Elas possuem tanta importância quanto a função findAllShortestPaths(). Basicamente, a função getMin() decide a direção de exploração no grafo escolhendo o nó com o melhor (menor no nosso caso) custo possível que ainda não está na árvore. Outro detalhe importante é que a ação de adicionar um vértice do grafo em uma árvore apartada é o fator determinante para que aquele vértice não seja mais analisado pelo algoritmo.

A função updateShortestPathArray() atua na atualização do array de menores caminhos e avalia se o custo de um trajeto inteiro vale a pena ou não de ser computado como ou não como um melhor caminho. Sem essa função não teríamos os resultados que tanto desejamos. No fim das contas, o que esperamos é a Tabela 2, já mostrada acima, e a Figura 3 que representa a árvore gerada pelo algoritmo.
Figura 3 - Árvore montada pelo algoritmo de Dijkstra
 Com isso fechamos a série Grafos do blog. Se olharmos para trás veremos que falamos e aprendemos muito. Começamos de forma humilde para que no fim pudéssemos fechar de forma triunfal. E a idéia sempre foi essa, discutir e aprender cada vez mais. Se você chegou até esse ponto da leitura fico feliz, espero ter feito a diferença e agradado.

Até a próxima minha boa gente ! 😘

Leia nossa postagem anterior: Você já parou para pensar sobre controle transacional ?

Download do código-fonte
Github: https://github.com/PrecisoEstudarSempre/Graphs.git

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, envie um e-mail ou uma mensagem na nossa página do Facebook.

E-mail: precisoestudarsempre@gmail.com
Facebook: https://www.facebook.com/precisoestudarsempre/
Canal Preciso Estudar Sempre: https://www.youtube.com/channel/UCUoW8dS38rXr0a5jWU57etA

Referências

LAFORE, ROBERT; Estruturas de Dados e Algoritmos em Java; 2004
Leia Mais ››

sexta-feira, 23 de junho de 2017

Você já parou para pensar sobre controle transacional ?

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Começo este post com a seguinte pergunta: você já parou para pensar sobre controle transacional ? Já tirou cinco minutos do seu dia para pensar no assunto ?

Minha pergunta pode parecer descabida ou até um pouco sentimental pois não fiz nenhuma apresentação sobre o tema. Contudo, faço essa pergunta porque em minha vivência de mercado já me deparei com muitos programadores que não tem a mínima idéia sobre o assunto. Obviamente eles sabem o que é uma transação, um commit ou um rollback. Porém pouquíssimos sabem implementar estruturas de controle transacionais do zero.

Interessante !

Tal fato além de ser extremamente preocupante na minha opinião, ocorre porque este tipo de questionamento não é mais necessário no mercado de trabalho atualmente. A maioria senão todos os projetos utilizam tecnologias que através da inversão de controle (IoC) implementam seu próprio controle transacional. Como resultado, oferecem interfaces bem definidas e limpas para seus usuários, tornando assim trabalho deles mais fácil e ágil. Aí está o ponto que me causa preocupação, o excesso de facilidade. Excesso o qual tende a tornar as pessoas, no caso desenvolvedores, mais preguiçosos causando um esquecimento de conceitos importantes.

Se você acabou de ler o parágrafo acima está pensando que estou falando de tecnologias como Spring, EJB 3 ou Hibernate. Acertou, estou falando exatamente disso ! Mas não vá pensar que estou condenando o uso de tais ferramentas. Pelo contrário, acho super correto utilizá-las e se você não as conhece, recomendo que estude-as. Entretanto, nunca se esqueça disso: não fique preso somente ao funcionamento das ferramentas existentes, entenda como elas funcionam ! O ponto central do meu raciocínio é justamente este, é necessário entender como “por detrás dos panos” o Spring, por exemplo, consegue executar rollback em uma transação inteira, onde nesta várias tabelas sofreram operações. Obviamente não podemos esquecer que ele não somente consegue fazer isso, mas como ainda disponibilizar uma forma que você escreva sua camada DAO (Data Access Object) de forma não acoplada a sua fachada de negócio, ou serviço.

A esta altura do campeonato você já deve ter notado que esta não é uma discussão trivial. Estamos lidando com uma situação arquitetural complexa com vários padrões de projeto, conceitos e preocupações. Logo algumas amostras de código e um diagrama de classes poderiam ajudar a trazer para a realidade o que estou propondo.
Figura 1 - Diagrama de classes proposto
É importante deixar claro aqui que o modelo proposto não é a solução para todos os problemas do universo. Como sempre digo, não existem balas de prata na computação. Talvez você leia esta postagem, encontre vários erros e monte um modelo melhor. Isso acontece, soluções desse tipo são de caráter evolutivo. Então, fique a vontade para contribuir.

Começaremos a explicação deste modelo analisando a classe DAO. Como o nome já mostra, ela segue o padrão DAO (Data Access Object), logo sua responsabilidade é lidar com abstrações de acesso à dados na base de dados. Trabalhando em conjunto com ela existe a classe HelperDAO, e como o seu nome também deixa claro, ela é uma classe auxiliar. Sua responsabilidade é a de prover métodos auxiliares de fechamento de statements e result sets. A interface ConnectionInjector possui um único método, responsável pela injeção da instância do objeto de conexão de banco de dados na classe DAO. Esta injeção de fato ocorre pela implementação da interface pela classe. Mais a frente entenderemos o motivo disso.

As classes que tratam o negócio são representadas pela classe BusinessFacade e é nela que todo o controle transacional acontece. Ali deve estar o nosso foco. Neste momento você pode estar se questionando da seguinte forma: se um contexto transacional é relacionado diretamente a operações de banco de dados, porque implementá-lo na camada de negócio ? Isto não tornaria a implementação altamente acoplada ?

Não te condeno por tal dúvida, pois eu já a tive algum tempo atrás, mas refletindo sobre assunto cheguei a conclusão de que eu estava errado. O negócio é o elemento que dita o que deve estar ou não na transação, por exemplo: toda vez que um usuário for cadastrado no sistema X, ele deve ser imediatamente cadastrado na lista de usuários que tem dívidas em aberto. Então em nossa transação devem existir instruções de escrita nas tabelas de usuários e de devedores. Caso haja algo de errado nesta operação como um todo, todas as operações devem ser desfeitas. Agora imagine se para cada operação uma nova transação fosse aberta, como controlar todo o contexto de negócio ? Se uma operação falhar como comunicar a próxima ou a anterior ? Essas são as reais preocupações que devem tomar a mente de um desenvolvedor/arquiteto quando ele planeja montar seu contexto transacional.

Para que a classe de negócio não tenha mais de uma responsabilidade, fato que fere o primeiro princípio da S.O.L.I.D, ela utiliza a ajuda de uma classe helper, ou seja, uma classe auxiliar. É aí que a classe HelperTransactionalControl ganha espaço, pois nela estão concentradas todas as funções necessárias para se criar ou não um contexto transacional, e caso criado, controlá-lo. Lembre-se que leituras em banco podem ser feitas e não há a necessidade de uma transação para isso. 

Um ponto interessante nesta classe é o método injectConnection(ConnectionInjector…). Note que ele recebe um varargs do tipo ConnectionInjector e que esta é a interface que todas as classes DAO implementam. Na implementação deste método ele faz uma chamada ao método injectConnection(Connection) da interface ConnectionInjector, ou seja, ele injeta um objeto de conexão em todas as instâncias DAO utilizadas. Então, na classe de negócio a única preocupação que deve ser considerada é a de invocar este método da classe helper, passando como parâmetro todos as instâncias DAOs utilizadas no momento. Fiz essa escolha de projeto pois não me agradava o fato de talvez passar como parâmetro, no construtor de todas as instâncias DAOs, um objeto do tipo Connection. Não é responsabilidade da classe que trata o negócio lidar com esse objeto, logo escolhi fazer isso “por debaixo dos panos”.

A interface que a classe de negócio implementa é uma simples escolha de programação orientada a interface. Com este recurso posso facilmente trocar a implementação de um contexto de negócio sem ter que me preocupar com quem já consumia informações a partir dele.

--------------------------------------------------------------------------------------------------------------------------
OBSERVAÇÃO: O varargs da linguagem Java é uma categoria de tipo de dados que se comporta como se fosse um array. Logo, se o seu método recebe um varargs do tipo inteiro, você pode passar os valores tanto separados por vírgula como por um array, exemplo: foo(1,2,3,4,5) ou foo(new int[]{1,2,3,4,5}).
--------------------------------------------------------------------------------------------------------------------------

Acredito que com as amostras de código abaixo tudo o que foi discutido ficará mais claro.

ConnectionInjector.java


1
2
3
4
5
6
7
package src.model.dao;
 
import java.sql.Connection;
 
public interface ConnectionInjector {
 void injectConnection(Connection connection);
}

Classe HelperDAO.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package src.model.dao.helper;
 
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
 
public class HelperDAO {
 
 public void closeStatement(Statement statement)
   throws SQLException {
  if (statement == null) {
   throw new IllegalArgumentException(
     "Errorcode: 102 - Statement nulo.");
  }
  try {
   statement.close();
  } catch (SQLException e) {
   throw new SQLException("Errorcode: 102 - Erro no fechamento do objeto Statement.", e);
  }
 }
 
 public void closeStatementAndResultSet(Statement statement,
   ResultSet resultSet) throws SQLException {
  this.closeStatement(statement);
  if (resultSet == null) {
   throw new IllegalArgumentException(
     "Errorcode: 102 - ResultSet nulo.");
  }
  try {
   resultSet.close();
  } catch (SQLException e) {
   throw new SQLException("Errorcode: 102 - Erro no fechamento do objeto ResultSet.", e);
  }
 }
}


DAO.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package src.model.dao;
 
import src.model.dao.helper.HelperDAO;
import java.util.List;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
 
public class DAO implements ConnectionInjector{
 private Connection connection;
 private HelperDAO helperDAO;
 
 public DAO(){
  this.helperDAO = new HelperDAO();
 }
 
 @Override
 public void injectConnection(Connection connection) {
  this.connection = connection;
 }
 
 public List findAll() throws IntegrationException {  
  PreparedStatement preparedStatement = null;
  ResultSet resultSet = null;
  List results = new ArrayList<>();
  try{
   String sql = "SELECT COLUMN1, COLUMN2, COLUMN3 FROM TABLE";  
   preparedStatement = this.connection.prepareStatement(sql);
   resultSet = preparedStatement.executeQuery(sql);
   while (resultSet.next()) {
    MyObject myObject = new MyObject();
    myObject.setMyProperty1(resultSet.getLong("COLUMN1"));
    myObject.setMyProperty2(resultSet.getString("COLUMN2"));
    myObject.setMyProperty3(resultSet.getString("COLUMN3"));
    
    results.add(myObject);
   }
   return results;
  } catch (SQLException e){
   throw new SQLException("Errorcode: 104 - Erro de SQL na listagem de todos os objetos.", e);
  } finally {
   helperDAO.closeStatementAndResultSet(preparedStatement, resultSet);
  }
 }
 
 private void insert(MyObject myObject) throws SQLException {
  PreparedStatement preparedStatement = null;
  try {
   int paramPos = 0;
   String sql = "INSER INTO TABLE (COLUMN1, COLUMN2, COLUMN3) VALUES (?,?,?)");
   preparedStatement = this.connection.prepareStatement(sql);
   preparedStatement.setLong(paramPos++, myObject.getProperty1());
   preparedStatement.setString(paramPos++, myObject.getProperty2());
   preparedStatement.setString(paramPos++, myObject.getProperty3());
   preparedStatement.execute();
  } catch (SQLException e) {
   throw new SQLException(
     "Errorcode: 104 - Erro de SQL no cadastro do objeto.",
     e);
  } finally {
   helperDAO.closeStatement(preparedStatement);
  }
 }
}


HelperTransactionalControl.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package src.model.business.helper;
 
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
 
import src.model.dao.ConnectionInjector;
import src.model.exception.IntegrationException;
 
public class HelperTransactionalControl {
 
 private static final String USER_DATABASE = "USER";
 private static final String PASSWORD_DATABASE = "PASSWORD";
 
 private Connection connection;
 
 public void openConnection() throws IntegrationException {
  try {
   Class.forName("com.jdbc.mysql.Driver");
   this.connection = DriverManager.getConnection(
     "jdbc:mysql://localhost:3306/DATABASE",
     USER_DATABASE, PASSWORD_DATABASE);
  } catch (SQLException | ClassNotFoundException e) {
   throw new IntegrationException("Errorcode: 100 - Erro na criação de uma conexão ao banco de dados.", e);
  }
 }
 
 public void injectConnection(ConnectionInjector... daosObject){
  for (ConnectionInjector daoObject : daosObject) {
   daoObject.injectConnection(connection);
  }
 }
 
 public void endConnection() throws IntegrationException {
  if (connection == null) {
   throw new IllegalArgumentException(
     "Errorcode: 102 - Connection nula.");
  }
  try {
   connection.close();
  } catch (SQLException e) {
   throw new IntegrationException("Errorcode: 102 - Erro no fechamento do objeto Connection.", e);
  }
 }
 
 public void beginTransaction() throws IntegrationException{
  try {
   connection.setAutoCommit(false);
  } catch (SQLException e) {
   throw new IntegrationException("Errorcode: 103 - Erro na abertura da transação de banco.", e);
  }
 }
 
 public void commitTransaction() throws IntegrationException{
  try {
   connection.commit();
  } catch (SQLException e) {
   throw new IntegrationException("Errorcode: 103 - Erro na commit da transação de banco.", e);
  }
 }
 
 public void rollbackTransaction() throws IntegrationException{
  try {
   connection.rollback();
  } catch (SQLException e) {
   throw new IntegrationException("Errorcode: 103 - Erro no rollback da transação de banco.", e);
  }
 }
}


BusinessFacade.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package src.model.business;
 
import java.sql.SQLException;
import java.util.List;
 
import src.model.dao.DAO;
import src.model.exception.IntegrationException;
import src.model.business.helper.HelperTransactionalControl;
 
public class BusinessFacade implements IBusinessFacade{
 
 private HelperTransactionalControl helper;
 
 public BusinessFacade() {
  helper = new HelperTransactionalControl();
 }
 
 @Override
 public MyObject insert(MyObject myObject) throws IntegrationException {
  try {
   DAO dao = new DAO();
   
   helper.openConnection();
   helper.beginTransaction();
   helper.injectConnection(dao);
   dao.insert(myObject);
   helper.commitTransaction();
   
   return myObject;
  } catch (SQLException e) {
   helper.rollbackTransaction();
   throw new IntegrationException(e);
  } finally {
   helper.endConnection();
  }
 }
 
 @Override
 public List findAll() throws IntegrationException{
  try {
   DAO dao = new DAO();
      
   this.helper.openConnection();
   this.helper.injectConnection(dao);
   return dao.findAll();
  } catch (SQLException e) {
   throw new IntegrationException(e);
  } finally {
   this.helper.endConnection();
  }
 }
}


Note que como consequência da implementação do projeto de software proposto, a classe de tratamento do negócio ficou bem enxuta e com responsabilidades bem definidas. Não achei necessário expor aqui o código-fonte da exceção especializada (IntegrationException) e da interface IBusinessFacade porque seus conteúdos são bastante triviais e iriam causar mais volume na postagem, talvez tornando a leitura mais cansativa.

Como dito anteriormente, sinta-se livre para criticar, existem várias formas de se fazer o mesmo e é isso que engrandece um projeto de software, mas se você quiser elogiar não se acanhe 😋.

Chegamos ao fim de mais um post e neste aqui devo grandes agradecimentos a um amigo e mentor, o imenso Luís Marcelo Bruckner. Muito obrigado meu amigo.

Até a próxima minha boa gente ! 😘
Leia nossa postagem anterior: Grafos ponderados - Grafos
Download do código-fonte
GoogleDrive: clique aqui 
Dropbox: 
Dúvidas !? Sugestões ?! Críticas ou elogios ?!
Deixe aí nos comentários, envie um e-mail ou uma mensagem na nossa página do Facebook.
E-mail: precisoestudarsempre@gmail.com
Leia Mais ››

quarta-feira, 31 de maio de 2017

Grafos ponderados - Grafos

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

A partir de agora, reserve sua mente para um novo conceito. Pegue tudo o que você já aprendeu e guarde, pois será necessário. Você está prestes a conhecer um novo tipo de grafo, com novas características e ferramentas. Conheça os Grafos Ponderados.

Resumidamente, grafos ponderados são grafos com pesos em suas arestas (Figura 1). Entenda pesos como valores e não como quilos. São utilizados para determinar qual o melhor trajeto adotar dado o problema. Pense na seguinte situação: você quer sair do centro da cidade do Rio de Janeiro e chegar no Cristo Redentor. Como chegar lá ? Existem vários caminhos. Qual deles é o melhor ? Isso depende de você. Se está com pressa por causa de tempo, talvez aquele caminho mais curto porém mais perigoso seja melhor do que aquele mais seguro e longo, ou talvez se um trajeto com mais paisagens for mais importante do que rapidez, um outro caminho pode ser o ideal. Visto que podem existir diversas possibilidades de caminhos a serem feitos, um critério de escolha deve ser escolhido e em cima dele as diversas arestas que conectam o ponto A ao ponto B ganharão seus respectivos pesos.
Figura 1 - Diferenças entre um grafo ponderado e não ponderado


Um grafo ponderado pode ser tanto orientado quanto não orientado (Figura 2) e essa orientação é a mesma já vista anteriormente, logo os problemas que essa característica traz também são os mesmos e obviamente acabam se acumulando com os novos desafios oriundos das arestas ponderadas. Estruturas como matrizes ou listas de adjacências também aparecem aqui, logo nenhuma surpresa quanto a isso. O que realmente vai diferenciar este tipo de grafo são seus pesos em suas arestas e os algoritmos originados a partir da análise dos mesmos. Tais diferenciais tornam a implementação um pouco diferente do que já estávamos acostumado.
Figura 2 - Diferenças entre um grafo ponderado orientado e um não orientado
WeightedGraph.java
Como dito antes, alguns métodos não são lá uma surpresa, contudo alguns destaques podem ser feitos. Note que não existe inicialização com 0’s da matriz de adjacência aqui, pois ela não é mais uma matriz de inteiros e sim de objetos do tipo Edge. Como é de conhecimento de todos, variáveis de instância em Java tem seu valor default como null, logo automaticamente a matriz tem valores nulos em todas as suas posições e isso é o bastante para saber se existe uma aresta ali ou não.

O método de adição de aresta deixa claro através de seu nome que são arestas não orientadas que são adicionadas e ele recebe como parâmetro o peso da aresta. Realizei a mudança no nome com a finalidade de deixar o código mais intuitivo. Lembre-se que pelo fato das arestas não serem orientadas é preciso marcar a matriz duas vezes e que agora será inserido um objeto do tipo Edge ao invés de um inteiro. O resto da classe é mais do mesmo que já estamos acostumados.

Edge.java

Esta classe representa uma aresta, podendo ser orientada ou não, de um grafo ponderado. Lembre-se que a orientação é ditada na matriz. Dentro dela estão todas as informações necessárias de uma aresta, como: vértice de origem, destino e seu peso.

WeightedGraphApp.java

Classe cliente, tendo como responsabilidades a criação e exibição de um grafo ponderado.

Com isso, chegamos ao fim de mais uma postagem.

Estamos chegando ao fim da série Grafos. Faltam somente mais alguns assuntos para encerramento total desta série, não perca nenhum deles. Após esta série de assuntos começaremos uma nova com um novo formato que ainda é surpresa para tornar a nossa experiência a mais dinâmica possível.

Até a próxima minha boa gente ! 😘

Leia nossa postagem anterior: Indicações de livros - Estruturas de dados e Algoritmos em Java

Download do código-fonte
Link do repositório GitHub: https://github.com/PrecisoEstudarSempre/Graphs.git

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, envie um e-mail ou uma mensagem na nossa página do Facebook.

E-mail: precisoestudarsempre@gmail.com
Facebook: https://www.facebook.com/precisoestudarsempre/
Canal Preciso Estudar Sempre: https://www.youtube.com/channel/UCUoW8dS38rXr0a5jWU57etA
Leia Mais ››

quarta-feira, 17 de maio de 2017

Indicações de livros - Estruturas de dados e Algoritmos em Java

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Para dar aquela quebrada na rotina, neste mês trago a indicação de um excelente livro através de um vídeo do nosso canal do YouTube (links lá embaixo). Não perca, ficou muito bom. :D

Livro: Estruturas De Dados E Algoritmos em Java
Autor: Robert Lafore
Editora: CIENCIA MODERNA
Sumário:
  • Capítulo 1 - Visão geral
  • Capítulo 2 - Vetores (se você já sabe, pode pular)
  • Capítulo 3 - Ordenação simples (recomendo muito)
  • Capítulo 4 - Pilhas e filas (importante para qualquer desenvolvedor)
  • Capítulo 5 - Listas encadeadas (importante para qualquer desenvolvedor)
  • Capítulo 6 - Recursão (diferencia homens de meninos)
  • Capítulo 7 - Ordenação avançada (recomendo muito)
  • Capítulo 8 - Árvores binárias (importante para qualquer desenvolvedor)
  • Capítulo 9 - Árvores rubro-negras (inside the matrix)
  • Capítulo 10 - Árvores 2-3-4 e armazenamento externo (inside the matrix)
  • Capítulo 11 - Tabelas Hash (importante para qualquer desenvolvedor)
  • Capítulo 12 - Heaps (inside the matrix)
  • Capítulo 13 - Grafos (se você gosta de assuntos mais complicados, aqui é o lugar)
  • Capítulo 14 - Grafos ponderados (se você gosta de assuntos mais complicados, aqui é o lugar)
  • Capítulo 15 - Quando usar o que ? (um dos melhores capítulos)

Até a próxima minha boa gente ! 😘

Leia nossa postagem anterior: O algoritmo Warshall - Grafos

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, envie um e-mail ou uma mensagem na nossa página do Facebook.

E-mail: precisoestudarsempre@gmail.com
Facebook: https://www.facebook.com/precisoestudarsempre/
Canal Preciso Estudar Sempre: https://www.youtube.com/channel/UCUoW8dS38rXr0a5jWU57etA
Leia Mais ››

sexta-feira, 28 de abril de 2017

O algoritmo Warshall - Grafos

Bem-vindos ao blog Preciso Estudar Sempre. Meu nome é João Paulo Maida e minha paixão é estudar.

Até este momento já vimos bastante coisa sobre grafos. Estudamos seus conceitos mais fundamentais, tipos de buscas, árvores geradoras mínimas e por último, orientação. A orientação se torna um grande divisor de águas quando o assunto é grafos. Isto acontece pelo simples fato de que a adição de direção nas arestas muda completamente as regras do jogo. Se antes era possível iniciar um trajeto em um determinado ponto e terminar em outro, agora não é mais, e tal fato se torna uma pergunta. Como determinar todos os caminhos possíveis de um grafo ? Quais são as aplicações disso no mundo real ?

Bem, a segunda pergunta é fácil de responder. Basta pensar em um aeroporto, como o aeroporto do Galeão no Rio de Janeiro, e tentar determinar quais viagens são possíveis de fazer a partir dele. Cada aeroporto representaria um nó e cada trajeto direto (sem escalas) possível uma aresta. Logo, se existe uma viagem que parte do Galeão e vai para Frankfurt na Alemanha, teremos dois nós G e F (representados pelas primeiras letras dos aeroportos) ligados por uma seta iniciando em G e indo para F. Em seguida, se somente a partir de Frankfurt é possível ir para Varsóvia, na Polônia, então será necessário realizar o trajeto Galeão -> Frankfurt -> Varsóvia. Não existe outro caminho possível.

Para o nossa primeira pergunta o que era problema se torna trivial, pois estamos analisando um exemplo muito pequeno mas imagine um cenário real, onde você pode ir para qualquer lugar do mundo e muitas vezes só existem caminhos de ida e não de volta. Acredito que as coisas se compliquem um pouco. Então determinar um procedimento capaz de realizar esse mapeamento completo se torna crucial.
Grafo orientado G com sua tabela de conectividade. Possui cinco vértices e quatro arestas.
Figura 1 - Grafo orientado G com sua tabela de conectividade
Para o grafo da Figura 1 é fácil determinar quais caminhos podem ser feitos, pois ele é pequeno e junto à ele é apresentado sua tabela de conectividade. Esta tabela tem como função mostrar quais nós são alcançáveis a partir de um determinado nó, representado pela primeira letra da sequência. Logo, a partir de A é possível ir até B, C e E. Isto já representa um avanço, visto que possuímos um catálogo completo sobre quais caminhos podemos fazer. Contudo, o foco deve ser em fornecer uma forma direta de saber se um caminho é possível ou não, exemplo: é possível a partir de A chegar à D ?

Analisando rapidamente a tabela é possível concluir que isto é uma inverdade, e somente é possível chegar a essa conclusão pois vimos letra após letra da sequência que começa com A. Tal processo é custoso visto que leva tempo O(N), onde N é o número médio de nós alcançados a partir de um determinado nó. Mas, conforme citado acima, procuramos uma forma direta de obter essa informação, procuramos uma forma que leve tempo O(1) (LAFORE, ROBERT).

Tal almejada forma é o algoritmo de Warshall e sua ideia é extremamente simples. Ele utiliza uma estrutura já conhecida, a matriz de adjacência, e insere novas informações nela, a fim de torná-la mais rápida. A matriz melhorada gera um grafo chamado de fechamento transitivo do grafo original (LAFORE, ROBERT).

Para entendermos como funciona o algoritmo, antes precisamos tomar conhecimento da matriz de adjacência do grafo atual.
Matriz de adjacência do grafo G
Figura 2 - Matriz de adjacência do grafo G
----------------------------------------------------------------------------------------------------------------
LEMBRETE: As linhas representam as origens de uma aresta e as colunas os fins, ou seja, se na célula AB existe o número 1 logo existe uma aresta de A para B.
----------------------------------------------------------------------------------------------------------------

O algoritmo deve percorrer todas as células da matriz. Caso encontre 1 em alguma célula, ele saberá que ali existe uma aresta. Para cada aresta encontrada ele deve percorrer todas as células da coluna correspondente à sua linha, ou seja, se foi encontrado 1 na célula AB, a coluna A deverá ser percorrida completamente. Caso nesta coluna exista algum 1, ele saberá que existe uma aresta chegando naquele nó, onde a origem será a linha dessa célula. Neste exato momento é concluído que existem dois caminhos que partilham um mesmo nó. Então, se existe tal caminho ele pode ser “encurtado” em um caminho só, logo um 1 é adicionado na célula correspondente a esse caminho. Confuso ? Nem um pouco. A Figura 3 mostra como o processo funciona.
Funcionamento do algoritmo de Warshall
Figura 3 - Funcionamento do algoritmo de Warshall
Acredito que com a imagem a explicação do parágrafo acima ficou mais fácil de ser entendida. Em uma primeira instância o algoritmo encontra a aresta A -> B, mas verificando a coluna A conclui que não existem arestas chegando nele logo a partir deste nó só partem arestas. A verificação da linha A foi terminada, a linha B vem em seguida.

A verificação começa e a aresta B -> C é descoberta. A coluna B é verificada e a aresta A -> B é encontrada, logo o nó B é comum as duas arestas e através dele é possível conectar A à C. Por fim, a célula AC é marcada com 1 para representar que existe uma aresta que liga esses dois nós. Este procedimento é repetido para todos os nós das linhas da matriz. Quando não houver mais linhas para serem processadas, o algoritmo chegou ao fim.

Abaixo apresento uma implementação feita em Java para este algoritmo. Como já é de costume é importante deixar bem claro que escolhi Java por uma questão de afinidade e nada impede que você escolha uma linguagem de seu gosto. Nosso foco aqui não é realizar as melhores práticas da linguagem, mas sim aprender como o algoritmo funciona.

Graph.java
O algoritmo é bem simples visto que são três laços de repetição com algumas condicionais. O primeiro laço consiste na varredura das linhas da matriz, o segundo das colunas. Caso haja uma aresta, a procura na coluna correspondente é feita no terceiro laço. Se todas as correspondências forem encontradas o novo caminho é gravado na matriz. Todo este procedimento é repetido para todas as linhas da matriz fazendo com que o algoritmo chegue ao fim e torne o acesso à caminhos de um grafo O(1).

WarshallAlgorithm.java
Esta classe é apenas uma classe cliente, sua única responsabilidade é criar grafos, adicionar arestas e vértices, e executar o algoritmo de Warshall. Exibir o fechamento transitivo do grafo original e checar possíveis caminhos também são operações realizadas aqui.

A partir dessa postagem entraremos em uma outra classe de grafos, os grafos ponderados. Aqui veremos que é possível atribuir pesos para as arestas e ver qual é o melhor caminho a ser feito, assim como os programas de GPS fazem. Mais um passo no longo caminho do entendimento desta extraordinária teoria foi dado.

Até a próxima minha boa gente ! 😘

Leia nossa postagem anterior: A orientação em grafos - Grafos

Download do código-fonte
Link do repositório GitHub: https://github.com/PrecisoEstudarSempre/Graphs.git

Dúvidas !? Sugestões ?! Críticas ou elogios ?!

Deixe aí nos comentários, envie um e-mail ou uma mensagem na nossa página do Facebook.

E-mail: precisoestudarsempre@gmail.com
Facebook: https://www.facebook.com/precisoestudarsempre/
Canal Preciso Estudar Sempre: https://www.youtube.com/channel/UCUoW8dS38rXr0a5jWU57etA

Referências

LAFORE, ROBERT; Estruturas de Dados e Algoritmos em Java; 2004
Leia Mais ››