Você está na página 1de 7

i i Arquitetura e Design de Software Caelum 2009/11/4 8:38

i i

Arquitetura e Design de Software

Inverso de Controle: Cad a minha chave de fenda?


Quando o assunto organizao de classes e objetos, as dependncias entre as diversas partes do sistema so um assunto delicado, e devemos nos esforar ao mximo para diminuir o acomplamento. Para ilustrar a discusso, podemos aproveitar um caso recorrente em diversos projetos de Software. Imagine que estamos tentando encapsular todo o acesso a dados de uma aplicao, centralizando todo este acesso em objetos especcos para este m. Para tal, aplicaremos o design pattern Data Access Object (DAO):
public class ProjetoDAO { public void salva(Projeto projeto) { // ... } public void remove(Projeto projeto) { // ... } public Projeto carrega(Integer id) { // ... } }

Partindo do princpio que estamos usando diretamente JDBC para o acesso ao banco, precisamos de uma conexo (java.sql.Connection) em todos os mtodos do DAO. O uso de algum framework para acesso a dados ou de mapeamento objeto relacional no resolveria o problema j que precisaramos de algo anlogo a uma Session ou EntityManager. O problema que o DAO foi criado para encapsular os detalhes de acesso aos dados, porm precisa de alguns recursos (conexes, neste caso) para fazer o seu trabalho. Seria o mesmo caso com um EnviadorDeEmails que precisa de uma conexo com o servidor de emails, por exemplo. Como podemos obter uma referncia para Connection? Como fazer para conseguir estas dependncias? A primeira alternativa seria abrir conexes diretamente nos mtodos que precisam delas, deixando os detalhes do trabalho sujo por conta do prprio DAO:
public class ProjetoDAO { public void salva(Projeto projeto) throws ... { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost/test"; String usuario = "root"; String senha = "123"; Connection connection = DriverManager.getConnection(url, usuario, senha); // uso da conexo ...

i i i

i i Arquitetura e Design de Software Caelum 2009/11/4 8:38

i i

Design

connection.close(); } // outros mtodos }

O cdigo acima serve para conectar em uma base do MySQL. invivel repetir todo este cdigo em todos os outros lugares que precisam de conexo. Alm disso, ainda existem diversos fatos importantes a serem considerados, como o uso de um pool de conexo para gerenciamento mais inteligente dos recursos e externalizao das conguraes. O primeiro passo para amenizar o problema centralizar a responsabilidade de criar conexes em algum local. Desta forma, podemos depender apenas de uma interface e ignorar completamente os detalhes de criao de conexes (se a conexo nova, se veio de um pool, qual driver estamos usando, etc). O uso de uma factory nos permite esconder e centralizar os detalhes de criao de uma Conexo e trocar essa estratgia mais adiante, caso necessrio. Baixo acoplamento! Mas no o suciente. Discutiremos o assunto adiante, na seo Fbricas e o mito do baixo acoplamento.
public class ProjetoDAO { public void salva(Projeto projeto) throws SQLException { Connection connection = new ConnectionFactory().getConnection(); // uso da conexo ... connection.close(); } public void remove(Projeto projeto) throws SQLException { Connection connection = new ConnectionFactory().getConnection(); // uso da conexo ... connection.close(); } // outros mtodos }

O gerenciamento das conexes est bem mais simples, mas ainda no resolve o problema. Ser que com o cdigo deste jeito, conseguiramos salvar dois Projetos usando a mesma conexo? E executar dois mtodos do DAO dentro de uma mesma transao? Isto infelizmente no possvel pois, a cada nova invocao de mtodo, estamos adquirindo uma nova conexo. Essa uma m prtica conhecida como Sesso por Operao, Transao por Operao, ou ainda Conexo por Operao (Session/Connection per Operation).

i i i

i i Arquitetura e Design de Software Caelum 2009/11/4 8:38

i i

Arquitetura e Design de Software

Para tentar resolver o problema, alguns projetos tentam agrupar as diversas operaes que precisam da mesma conexo (ou transao) dentro de mtodos do DAO:
public class ProjetoDAO { public void salvaDois(Projeto p1, Projeto p2) { Connection connection = new ConnectionFactory().getConnection(); // .... connection.close(); } public void salvaVarios(Projeto ... projetos) { Connection connection = new ConnectionFactory().getConnection(); // ... connection.close(); } public void salvaAtualizaERemove(Projeto aSalvar, Projeto aAtualizar, Projeto aRemover) { Connection connection = new ConnectionFactory().getConnection(); // ... connection.close(); } }

Com quantos mtodos terminaremos neste DAO? Este um claro exemplo de pssima diviso de responsabilidades, j que o DAO faz coisas demais: abre conexo, cuida de transao, fecha conexo, alm da tarefa que realmente lhe cabe: acessar dados. Cada novo tipo de transao da aplicao exige um novo mtodo no DAO. Na Caelum, apelidamos esse padro de classe Sistema, que geralmente tem muitas linhas. De alguma forma precisamos permitir que a mesma conexo seja usada em diversos mtodos do DAO, portanto guard-la como atributo. Mas onde abrir a conexo?
public class ProjetoDAO { private Connection connection; public void inicia() { this.connection = new ConnectionFactory().getConnection(); } }

Criar um mtodo para iniciar o DAO e abrir a conexo resolve o problema, mas cria outro. Qualquer um pode esquecer de chamar o mtodo inicia(), fazendo com que Connection seja null. O nosso objeto DAO no um Bom Cidado (Good Citizen Pattern), j que no veio ao mundo com todas suas dependncias preparadas e pronto para servir. O princpio do Bom Cidado [10] diz que devemos sempre deixar nossos objetos em um estado consistente. Para isso, o uso do construtor essencial, onde so preenchidas todas as dependncias e conguraes necessrias para que o objeto possa

i i i

i i Arquitetura e Design de Software Caelum 2009/11/4 8:38

i i

Design

trabalhar adequadamente, sem a subsequente necessidade de invocar setters ou outros mtodos de inicializao e congurao. O princpio ainda vai alm, denindo algumas boas prticas na manipulao de excees, logging e programao defensiva, com o objetivo de que esses objetos sejam seguros em relao a cdigo de terceiros. Usando o construtor para resolver o problema e forando a aquisio da conexo no momento da instanciao do DAO, chegamos no seguinte cdigo:
public class ProjetoDAO { private Connection connection; public ProjetoDAO() { this.connection = new ConnectionFactory().getConnection(); // pode abrir uma transao } public void fecha() { // poderiamos consolidar a transao this.connection.close(); } }

A conexo aberta no construtor, porm Java no tem destrutores. Mesmo que tivesse, ou usssemos o mtodo finalize(), no seria a soluo j que nunca temos a garantia de quando um objeto ser coletado. fundamental termos controle total sobre onde conexes so abertas e fechadas, tendo em vista que esse um objeto caro como arquivos, threads, sockets e outros que usam de I/O ou chamadas ao sistema operacional. O mtodo fecha parece ser a soluo, j que agora podemos instanciar o DAO e invocar vrias operaes dentro da mesma conexo/transao. Ainda assim essa soluo possui alguns poblemas: O mtodo fecha() quebra o encapsulamento, j que expe um detalhe especco desta implementao de DAO que estamos usando: existe uma conexo que deve ser fechada. Se trocarmos a implementao do DAO para persistir em arquivos XML, por exemplo, no haveria mais necessidade de ter o mtodo fecha(), pois no existiria mais nenhuma conexo a ser fechada; A boa prtica diz: se eu abri, eu fecho! Desta forma, evitamos espalhar a responsabilidade de gerenciar o recurso que estamos manipulando. O problema que o DAO abre a conexo, mas no sabe quando fech-la e delega esta tarefa a quem usa o DAO; Os usurios da classe DAO cam responsveis por invocar o mtodo fecha() e saber onde fechar algo que nem foram eles que abriram. A responsabilidade de gerenciar conexes ca assim espalhada pelo sistema. Todos so responsveis por saber que uma conexo existe, quando aberta e quando deve ser fechada; No h erros de compilao quando algum esquece de chamar o mtodo fecha(), portanto ningum obrigado a cham-lo. Como a responsabilidade se espalha por

i i i

i i Arquitetura e Design de Software Caelum 2009/11/4 8:38

i i

Arquitetura e Design de Software

todo o sistema, a chance de algum esquecer de fechar a conexo aumenta muito (para no dizer que inevitvel). Isso gera os clssico problemas de vazamento de conexes, que no so fechadas e podem resultar no desastroso efeito do banco rejeitando novas conexes quando existirem muitas abertas. Todos estes problemas so graves, mas se ainda no so convincentes o suciente, aqui vai mais um: com esta abordagem ainda no possvel salvar um Projeto, atualizar um Usuario, e deletar uma Historia usando a mesma conexo, ou dentro da mesma transao. Como cada DAO tem a sua prpria conexo, no h como compartilhar a mesma entre vrios deles. E como resolver o problema? Conhea a histria do Alberto e o princpio da inverso de controle. Alberto, o apertador de parafusos Alberto um apertador de parafusos. Esta a sua responsabilidade e ele veio ao mundo apenas para fazer isto (Single Responsibility Principle [33]). Gelson o seu gerente e costuma pedir: - Alberto, aperte este parafuso aqui! Como um funcionrio dedicado, Alberto sempre vai com muita boa vontade atender. Ao chegar perto do parafuso, Alberto percebe que est sem chave de fenda. Ou seja, para fazer o seu trabalho, Alberto precisa antes de tudo de uma chave de fenda (assim como o DAO precisa de uma conexo). A primeira alternativa do Alberto ir pegar a chave de fenda na sala 143. Dentro desta sala, a chave ca no armrio do canto esquerdo, de nmero 75. Como est sempre trancado, Alberto sabe que a chave ca na portaria da empresa, com o Joaquim. Aps pegar a chave com o Joaquim, ir at a sala 143 e abrir o armrio 75, Alberto pega o estojo verde da direita, que contm a chave de fenda, retira a chave de l e pode, enm, apertar o parafuso para seu gerente! O grande problema aqui que o Alberto precisa saber muito mais do que veio ao mundo para fazer. Precisa saber o nmero da sala e do armrio, precisa saber que a chave est na portaria e conhecer o Joaquim, caso contrrio ele no conseguiria a chave. Alm de tudo, precisa saber que a chave est no estojo verde da direita. Um trabalho e s para poder apertar um parafuso. Tudo isto s porque ele precisava de uma chave de fenda. Pior ainda, Alberto no o nico que precisa da chave de fenda. O pedreiro tambm usa, o encanador, o eletricista, etc. Todos eles precisam saber como obter uma chave de fenda. O que acontece se algum mudar a chave de fenda de lugar? preciso avisar todo mundo! Alto acoplamento! E se algum esquece de devolver a chave? E se o gerente comprar mais chaves de fenda para poder paralelizar o trabalho e as coloca em outros armrios? O grande problema que todos devem saber como pegar uma chave de fenda. Os objetos vo atrs daquilo que precisam para fazer o seu trabalho. A outra alternativa de Alberto, quando descobre que est sem chave de fenda, cruzar os braos: - No trabalho sem uma chave de fenda.

i i i

i i Arquitetura e Design de Software Caelum 2009/11/4 8:38

i i

Design

Desta forma, parece que o gerente Gelson quem ter de ir atrs da chave de fenda para entregar ao Alberto. Mas Gelson no precisa fazer isso sozinho: ele pode pedir ao Manoel, que o pegador de chaves de fenda! Assim temos a chance de dividir responsabilidade no sistema. Chance esta, que no havia antes. Antes todos eram responsveis por saber como pegar uma chave. Agora podemos centralizar esta responsabilidade em apenas um local. importante que o ciclo de vida dos nossos recursos sejam tratados em locais centralizados, em especial quando esses recursos so caros. Ao dizer que os objetos no vo mais atrs de suas depedncias, mas que agora devem apenas receb-las de algum, estamos invertendo o controle. Inverso de Controle apenas uma questo de postura com relao ao desenvolvimento. Ao invs de fazer os nossos objetos irem atrs daquilo que precisam, fazemos com que recebam tudo j pronto e mastigado. Antes a classe ProjetoDAO era responsvel por controlar a abertura e o fechamento da conexo, assim como, possivelmente, o destino das transaes envolvidas. Invertendo o controle [19], em vez do DAO ser responsvel pelo ciclo de vida e detalhes sobre a conexo e transao, o seu invocador ser o novo responsvel por isso, j entregando para ele todas as dependncias (a chave de fenda) mastigadas. O cdigo do DAO ca mais simples e enxuto, pois agora no preciso se preocupar mais com os detalhes da conexo e transao:
public class ProjetoDAO { private Connection connection; public ProjetoDAO(Connection connection) { this.connection = connection; } // mtodos do DAO }

IoC, Injeo de Dependncias e Testes Inverso de controle uma tcnica h muito tempo discutida [34], e que hoje aparece bastante dado os containers de inverso de controle [24]. Existem diversas maneiras de inverter o controle de uma aplicao. At aqui, vimos a inverso atravs da injeo de dependncias por construtor, considerada uma das formas mais elegantes por, alm de tudo, respeitar o princpio do Bom Cidado. Mas h outros tipos, como a injeo por setter ou diretamente em atributos. At mesmo os EJBs apresentam alto grau de inverso de controle desde suas primeiras verses (o que no necessariamente indica facilidade durante a codicao). Mas quem far a ligao (wiring) entre a conexo e nosso DAO? Existe a possibilidade de se trabalhar com vrios frameworks que possam fazer essa injeo (outro termo para ligao; ligar, amarrar e injetar so termos que aparecem com frequncia). Injeo de dependncias uma das formas de implementar inverso de

i i i

i i Arquitetura e Design de Software Caelum 2009/11/4 8:38

i i

Arquitetura e Design de Software

controle, mas no a nica. Na injeo de dependncias, temos um dependente (o DAO) e uma dependncia (a conexo). Quem amarra tudo isso um provedor (provider ). Esse provedor pode ser desde um service locator, uma factory, ou ainda um framework. Repare que no h a necessidade sempre de um framework, tudo pode ser amarrado por cdigo escrito direto pelo programador; o importante isolar o gerenciamento do ciclo de vida dos objetos, de quem depende deles. Os primeiros frameworks a trabalharem com inverso de controle usavam essas diferentes tticas para trabalhar com as dependncias: Apache Avalon atravs de service locators, Spring Framework e XWork atravs de injeo por setters e Pico Container atravs de injeo por construtor. As novas especicaes de JSF e EJB possuem mais recursos para injeo de dependncias, e h ainda a discusso entre a JSR 299, Contexts and Dependency Injection - antiga WebBeans e inspirada no JBoss Seam, e a JSR 330, Dependency Injection for Java - apelidada de @Inject. A JSR 299 tem Gavin King, que tambm o criador do JBoss Seam, como lder e especica a injeo de dependncias dentro de continers, tendo como alvo o Java EE 6.0. J a JSR 330 especica a injeo de dependncias fora de continers, tendo como alvo o Java SE 7.0. Nasceu de um acordo entre os lderes das bibliotecas de injeo de dependncias mais famosas do mercado: Paul Hammant (PicoContainer), Bob Lee (Guice) e Rod Johnson (Spring framework). Apesar das divergncias entre os criadores das duas especicaes, o JCP enftico ao dizer que as duas JSRs precisam convergir para algo comum e no disputarem entre si. Como trabalhar com alguns desses frameworks, suas vantagens e desvantagens, ser visto em uma seo posterior. Inverso de controle, em especial por injeo de dependncias, esta no s intimamente ligado com o desacoplamento de classes, como tambm ao uso de testes de unidade. Como veremos, trabalhar constantemente com testes de unidade costuma implicar no uso de injeo de dependncias, caso contrrio a diculdade de testar as classes isoladamente torna-se uma enorme barreira: o cdigo ca muito acoplado.

i i i

Você também pode gostar