Por que criar uma interface para cada DAO? – design-pattern engenharia-de-software interface

Pergunta:


Vejo muito na internet a criação de uma interface para cada DAO do sistema. Eu entendo a importância de se usar interface principalmente para uma troca fácil de implementação, já encontrei inclusive uma explicação dizendo se você usa um banco de dados e troca pra outro é só criar DAO’s novos e avisar pra injeção de dependência quais são os novos que ela deve injetar… enfim, em tempos de ORM não vejo a necessidade de se trocar o DAO inteiro, dependendo como foi programado apenas uns ajustes na troca de BD.

Então se tenho um sistema com duas tabelas apenas Usuario e Pedido (por exemplo), qual seria a vantagem de eu criar as interfaces UsuarioDAO e PedidoDAO para depois criar as implementações delas UsarioDaoImlp e PedidoDaoImpl?

Autor da pergunta Flavio Andrade

marcus

Isso depende mais da “tecnologia ao redor” e dos planos para o futuro, e não é necessariamente uma regra geral.

Por exemplo, o que eu quero dizer com “tecnologia ao redor”? Se você está falando de Java, era comum certas tecnologias mais antigas como o J2EE até o 1.4 (renomeado para JavaEE a partir da versão 5 e muito simplificado) exigirem a criação de várias interfaces e factories. Ou se não obrigava, a quantidade de boilerplate era tanta que o programador acabava separando as partes de qualquer jeito para reaproveitar código e separar responsabilidades.

Existe um livro justamente sobre “repensar” certas práticas que podem ter ficado obsoletas com o tempo em novas versões dos frameworks Java:

http://www.amazon.com/Real-World-Patterns-Rethinking-Practices/dp/0557078326

Um dos motivos para a criação de interfaces é para possibilitar chamadas remotas de métodos, algo muito mais enfatizado no início do JavaEE. Se chamadas remotas estiverem nos planos, uma interface separada para os clientes chamarem será útil em vez de amarrá-los diretamente a uma classe com implementação que deverá estar no mesmo projeto.

Outro motivo para a interface é para separar e abstrair a instanciação do DAO. Usando uma Factory, o DAO pode ser qualquer coisa: uma classe normal, um proxy para uma chamada remota, um pool de objetos reaproveitados, etc. sendo acessado apenas pela interface, sem tocar no objeto real. Esse padrão de design em JavaEE é menos necessário na presença do CDI: com @Inject você fica livre para decidir depois como será a instanciação da sua classe, sem precisar escrever factories, e também sem precisar criar uma interface separada.

Obs.: não tenho experiência com a injeção do Spring para comparar com CDI, mas bem… nem tenho certeza se a pergunta é sobre Java mesmo ou se é independente de linguagem.

E um terceiro motivo para ter uma interface aquele que você falou na sua pergunta: separação normal entre interface e implementação.

Agora, se alguma dessas situações se aplica ao seu caso, aí tem que pensar com cuidado (nenhuma regrinha fácil substitui o pensar!). Em geral eu prefiro seguir o YAGNI, e não codificar nada cuja utilidade não seja previsível visível. É importante separar os módulos da sua aplicação, mas raramente precisa fazer picadinho transformando cada classe em 3 (interface, implementação e factory).

Muitas vezes as necessidades futuras são tão diferentes do previsto que vale a pena ter algo mais simples. O que acontece com mais freqüência? Trocar todo o banco de dados ou adicionar uma coluna nova ou regra de validação? Codifique para deixar fácil o que acontece com mais freqüência.

A principal vantagem está na evolução e na modularização do sistema.

Temos duas possíveis abordagens:

  1. Criar 20 interfaces DAO com 5 ou 6 métodos cada, você tem interfaces UsuarioDAO, PedidoDAO, ContaDAO, ClienteDAO… Quando você for criar o módulo de vendedores, você vai criar um VendedorDAO, alguns poucos métodos e um VendedorDAOImpl.
  2. Criar uma única gigantesca interface MegaDAO com uns 100 métodos lá. Quando você for criar o módulo de vendedores, você vai acrescentar mais alguns métodos lá e implementá-los no MegaDAOImpl.

Agora vamos analisar as vantagens e desvantagens de cada alternativa:

Observe que na abordagem 2, o seu MegaDAO não é uma interface estável no tempo, pois ela está sempre mudando. As implementações desta interface (MegaDAOImpl) também não são estáveis no tempo, pois estão sempre mudando. Sempre que você cria ou altera algum módulo de forma que o acesso ao banco de dados mude, o MegaDAO e o MegaDAOImpl vão sofrer alterações.

A abordagem 2 também dificulta a modularização, pois ela cria uma interface da qual todos os módulos do sistema dependem, e efetivamente ela amarra todas as partes do sistema umas com as outras em um único bloco monolítico, mesmo que as diferentes partes tenham pouca ou nenhuma relação entre si. Se você quiser depois separar o programa em dois, um para o setor de contabilidade e o outro para o setor de manutenção, o seu MegaDAO e todas as suas implementações (mesmo que seja uma só) vai te dar dor-de-cabeça, pois ele coloca todas as funcionalidades do sistema juntas, inclusive aquelas que forem dispensáveis para o cenário em questão. O resultado disso é o famoso software bloat.

Além disso, vamos supor que o módulo de consulta de nota fiscal tem uma peculiaridade: Ele utiliza dois bancos de dados distintos por qualquer razão especial. Na abordagem 1, este é um detalhe do ConsultaNotaFiscalDAO e de suas (possivelmente múltiplas) implementações. Se você tiver um único MegaDAO, vai precisar resolver isso com uma gambiarra ou com um refactoring completo, pois haverá uma grande quantidade de métodos aonde o uso de dois bancos de dados não faz nenhum sentido, e com isso há o risco (pode ser ou pode não ser que aconteça) de a gambiarra (ou a necessidade de refactoring completo) acabar cascateando uma série de mudanças em outras partes do sistema que não deveriam estar sofrendo alteração.

Normalmente a implementação da maioria dos DAOs será bem parecida: Ele vai no ORM e consulta, insere, altera e/ou exclui registros. No entanto “normalmente” e “a maioria” não é a mesma coisa do que “sempre” e “todos”. Se houver uma exceção, digamos, o PrazoDeEntregaDAO que ao invés de ir no banco de dados relacional, vai em um serviço externo, ele trará problemas na abordagem 2 e vai complicar o seu MegaDAOImpl.

Talvez você argumente que neste caso o PrazoDeEntregaDAO nem sequer deveria ser um DAO. Mas vamos supor que originalmente ele era um DAO e com o tempo ele evoluiu para este comportamento especial. Na abordagem 1, tudo que você vai precisar fazer vai ser trocar a implementação do DAO por uma implementação especial diferente do normal. Na abordagem 2, você vai estar ferrado.

Vamos supor que você esteja rascunhando como será a implementação do ChatDAO. Para isso você criou um mock rápido que salva as informações apenas na memória, o ChatDAOMockImpl. Você também usa esta implementação para fazer testes de unidade. Isso é simples e natural na abordagem 1. Já na abordagem 2, você se ferra de novo.

Na abordagem 2, a medida que o seu sistema cresce, o MegaDAO e o MegaDAOImpl também crescem. Imagine que depois de 5 anos, você acrescentou mais de 40 módulos distintos e tem um sistema de grande porte com vários desenvolvedores trabalhando. E também tem um MegaDAOImpl com umas vinte mil linhas de código que a sua IDE até tem dificuldade para abrir e os desenvolvedores constantemente reclamando de problemas de merge com o git ou svn nesta classe.

Enfim, o problema do seu DAO único é que você viola vários princípios de programação orientada a objetos:

  • A implementação do seu DAO único será uma god class, que consiste em um dos piores anti-padrões de programação orientada a objetos.
  • O seu sistema terá um alto grau de acoplamento, pois o seu DAO (tanto a interface quanto a implementação) amarram tudo a tudo.
  • O seu sistema terá um baixo grau de coesão, pois uma grande quantidade de funcionalidades não relacionadas entre si ou pouco relacionadas entre si estarão coexistindo na mesma classe.
  • Você estará violando o princípio do aberto-fechado, aonde as funcionalidades devem ser estensíveis e reutilizáveis sem a necessidade de modificação de código já existente. Com um DAO único, qualquer tentativa de aumentar as funcionalidades implicam em uma alteração de código.
  • Você estará violando o princípio da responsabilidade única, pois a sua interface DAO (bem como a implementação) terá um monte de responsabilidades distintas, e como resultado, será difícil de ser utilizada.
  • Você estará violando, de forma bem direta, o princípio de segregação de interfaces, que diz que várias interfaces de propósito específico são melhores do que uma única grande interface de propósito genérico.

A única vantagem da abordagem 2 é ter o código todo em um só lugar e simplificar um pouco os métodos de gerenciamento de conexão com o banco de dados, consultas genéricas e coisas assim. E é só isso. Esta vantagem é facilmente derrotada pela grande quantidade de desvantagens.

Você talvez possa argumentar, ah mas eu só tenho dois módulos, Usuario e Pedido, preciso mesmo separar? A resposta é que todo o sistema que se torna grande começa pequeno, então quanto antes eles forem separados, mais fácil vai ser a evolução do sistema.

Fonte

Related Posts:

Qual a diferença entre AppCompatActivity e Activity? – android android-activity
Pergunta: Qual a diferença da AppCompatActivity para Activity ? A partir de qual versão a AppCompatActivity foi adicionada ao Android? Autor da pergunta Luhhh A diferença reside ...
Como abreviar palavras em PHP? – php string
Pergunta: Possuo informações comuns como nome de pessoas e endereços, e preciso que elas contenham no máximo 30 caracteres sem cortar palavras. Exemplo: 'Avenida Natalino João Brescansin' ...
Qual é a finalidade de um parêntese vazio numa declaração Lambda? – c# expressões-lambda característica-linguagem
Pergunta: Criei um exemplo de uma declaração Lambda sem argumentos, entretanto, estou com duvidas referente a omissão do parêntese vazio () na declaração. Veja o exemplo: class ...
Boas práticas para URI em API RESTful – api rest restful
Pergunta: Estou com dúvida em relação às URIs de alguns recursos da api que estou desenvolvendo. Tenho os recursos projetos e atividades com relação 1-N, ...
Dúvidas sobre a integração do MySQL com Java – java mysql netbeans
Pergunta: Estou criando um sistema no NetBeans, utilizando a linguagem Java e o banco de dados MySQL. Escrevi o seguinte código para realizar a conexão ...
Qual é a finalidade da pasta Model do framework Inphinit? – php inphinit
Pergunta: No Inphinit micro-framework existe a pasta Model que fica dentro da pasta application, e nela é onde ficam as classes, mas eu estou muito ...
Uso do ‘@’ em variáveis – javascript typescript coffeescript
Pergunta: Vejo em algumas linguagens que compilam para javascript, como TypeScript e CoffeeScript, o uso do @ em variáveis, como também, casos em que o ...
Qual tamanho máximo um arquivo JSON pode ter? – json arquivo
Pergunta: Vou dar um exemplo para conseguir explicar minha duvida: Preciso recuperar informação de imagens vindas de uma API, esse banco de imagens me retorna JSON's ...
O que é Teste de Regressão? – terminologia engenharia-de-software testes
Pergunta: Na matéria de Teste de Software o professor abordou um termo chamado Teste de Regressão, isto dentro da disciplina de teste de software. Sendo ...
O que é um construtor da linguagem? – php característica-linguagem
Pergunta: Em PHP, já li e ouvi várias vezes a respeito dos Construtores da Linguagem. Os casos que sempre ouvi falar deles foi em casos ...
Função intrínseca para converter numérico para string – cobol
Pergunta: Estou a tentar saber se existe alguma função intrínseca do COBOL para converter um data numérico para string sem precisar usar a cláusula REDEFINES: ( ...
Porque usar implements? – java android
Pergunta: Qual a diferença entre usar btn.setOnClickListener(new OnClickListener() { e public class MainActivity extends Activity implements OnClickListener{ Estive fazendo um curso de Android e meu professor falou que ...
O que é XHTML e quando deve ser usado? – html xml xhtml
Pergunta: O que eu sei é que o XHTML precisa ser XML válido. Isso implica, por exemplo, que todas as tags precisam ser fechadas. Por ...
Uma placa aceleradora de vídeo pode melhorar o desempenho não-gráfico? [fechada] – desempenho
Pergunta: Para desenvolver em Ruby on Rails, eu utilizo aqui uma máquina virtual do VirtualBox com Ubuntu Server 14.04 sem interface gráfica instalada. Recentemente descobri uma ...
Concat() VS Union() – c# .net
Pergunta: Qual a diferença entre Concat() e Union() ? Quando usar Concat() e quando usar Union() ? Somente pode ser usado em list ? ...

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *