Usar padrões de arquitetura facilita o entendimento, o desenvolvimento e a manutenção do código. Devemos ter sempre em mente que nossas classes devem ter um baixo nível de acoplamento e um alto nível de coesão, mas como podemos definir isso?
Acoplamento
- Acoplamento é o nível de dependência que pode existir entre duas ou mais classes;
- Uma classe com acoplamento fraco não é dependente de muitas classes para fazer o que ela tem que fazer;
- Uma classe com acoplamento forte depende de muitas outras classes para fazer o seu serviço;
- A coesão Mede o quanto que uma classe e seus métodos fazem sentido;
- Uma classe com alta coesão possui responsabilidades bem definidas e são difíceis de serem desmembradas em outras classes;
- Uma classe com baixa coesão possui muitas responsabilidades, geralmente que pertencem a outras classes, e podem ser facilmente desmembradas em outras classes;
"Na engenharia de softwares, Inversão de controle é uma prática de progamação orientada a objetos aonde o controle de chamadas dos métodos é invertida em relação a programação tradicional, ou seja, o controle não é determinado diretamente pelo programador."
Muitas pessoas confundem Inversão de controle com injeção de dependência. Devemos observar que a inversão de controle é um conceito. Através da injeção de dependência, podemos atingir a inversão de controle, mas existem outras maneiras.
Para entender melhor o conceito de inversão de controle, precisamos de um exemplo. Antigamente, era muito comum encontrar programas do tipo:
print "Digite seu nome";
read nome;
print "Digite seu cpf";
read cpf;
SalvarDados();
Repare como o programa escrito controla as interações do usuário.
Para aplicar a inversão de controle em um programa como esse poderíamos fazer o seguinte:
when the user types in CAMPO1, read NOME;
when the user types in CAMPO2, read CPF;
when the user clicks SALVAR, call SalvarDados();
Ao utilizar os event handlers o controle da interação deixa de pertencer ao programa, e cabe ao usuário definir quando e em que ordem o programa deve salvar os dados.
"Early user interfaces were controlled by the application program. You would have a sequence of commands like 'Enter name', 'enter address'; your program would drive the prompts and pick up a response to each one. With graphical (or even screen based) UIs the UI framework would contain this main loop and your program instead provided event handlers for the various fields on the screen. The main control of the program was inverted, moved away from you to the framework." - Martin Fowler
Para atingir a inversão de controle podemos seguir dois passos básicos:
1 - Separar a parte O QUE FAZER da parte QUANDO FAZER
2 - Garantir que a parte QUANDO FAZER saiba o mínimo possível sobre O QUE FAZER e vice-versa.
Existem várias técnincas para cada um desses passos de acordo com a linguagem de programação sendo utilizada. Neste artigo iremos discutir uma delas: Injeção de Dependência.
Exemplo ilustrativo:
Problema: Existe uma classe em seu projeto que possui dependências de serviços ou componentes especificados em tempo de design. Neste exemplo, ClassA tem dependências em ServiceA e ServiceB.
Esta situação tem os seguintes problemas:
Para substituir ou atualizar as dependências (ServiceA e ServiceB, você precisa alterar o código de suas classes de origem (ClassA).
As implementações concretas das dependências tem que estar disponíveis em tempo de compilação.
Suas classes são difíceis de testar de forma isolada, porque eles têm referências diretas a dependências que não podem ser mockadas.
Suas classes contem código repetitivo para criar, localizar e gerenciar suas dependências.
Para resolver a situação acima podemos aplicar o pattern de Injeção de Dependência.
Conceito básico: Não instancie as dependências explicitamente em sua classe. Em vez disso, declare dependências em sua definição de classe e isole a implementação de um objeto da construção do objeto do qual ele depende.
Podemos implementar a injeção de dependência das seguintes maneiras:
- Injeção via Construtor;
- Injeção via Propriedades;
- Injeção via Interface;
- Injeção via Framework;
- Injeção usando Unity Application Block (http://www.microsoft.com/en-us/download/details.aspx?id=19372 );
public class Livro
{
private Autor _autor;
public Livro()
{
_autor = new Autor();
}
public string NomeDoAutor(Pedido pedido)
{
return _autor.Nome;
}
}
No cenário acima, a classe Livro possui uma dependência da classe autor. Repare que a classe livro implementa a construção da classe autor gerando um FORTE acoplamento entre as duas classes, ou seja qualquer alteração na classe autor irá afetar diretamente a classe Livro. Podemos aplicar a injeção de dependência de maneiras diferentes:
Constructor Injection: Consiste em passar as dependências de um objeto para o seu construtor.
public class Livro
{
private Autor _autor;
public Livro(Autor autor)
{
_autor = autor;
}
public string NomeDoAutor(Pedido pedido)
{
return _autor.Nome;
}
}
Nesta implementação a classe Livro não tem mais a responsabilidade de criar uma instância de Autor.Como a classe Autor é criada não importa mais, ela apenas recebe uma instância e utiliza.
Se a classe Autor for alterada, a classe Livro fique imune a essas alterações pois depende apenas do objeto que foi injetado no seu construtor.
Uma da maiores desvantagens desta abordagem é o fato de que a classe Livro não pode ter um construtor padrão.
Para suportar a injeção de dependência sem precisar modificar o construtor da classe Livro, podemos usar a abordagem de injeção de dependência por propiedades (get / set).
Setter Injection: Esta abordagem não força a dependência ser passada via construtor. Ao invés disso, as dependências são definidas em propiedades que podem ser acessadas publicamente através dos Getters e Setters.
public class Livro
{
private IAutor _autor;
public Livro()
{
//Construtor independente
}
public IAutor Autor {
get {
if (_autor == null) {
throw new Exception("Autor não inicializado");
}
return _autor;
}
set { _autor = value; }
}
}
Este é o método mais comum para implementar a injeção de dependência, ele permite a extensão de recursos ou serviços que serão criados o mais tarde possível e somente quando necessário.
A desvantagem desta abordagem é que os objetos são expostos publicamente e isso quebra a regra de encapsulamento da programação orientada a objeto.
Interface Injection: Utiliza uma interface comum que outras classes necessitam implementar para injetar a dependência.
Unity Application Block: É um container de inversão de controle baseado na biblioteca “ObjectBuilder2” do “Entreprise Library” e que foi lançado como uma alternativa às bibliotecas “Spring.NET”, “ObjectBuilder” etc. Ele facilita a criação de aplicativos de baixo acoplamento e fornece aos desenvolvedores as seguintes vantagens:
- criação de objetos simplificados, especialmente para estruturas de objetos hierárquicos e dependências.
- abstração dos requisitos, o que permite aos desenvolvedores especificar dependências em tempo de execução ou por arquivos de configuração e simplificar a gestão de interesses transversais.
- aumento da flexibilidade, adiando a configuração do componente para o container.
- capacidade de localização de serviços, o que permite aos clientes armazenar em cache o container.
Existe um ótimo exemplo de projeto utilizando o UAB, http://www.codeproject.com/Articles/42524/A-basic-introduction-to-the-Unity-Application-Bloc
Baseado neste projeto podemos descrever o seguinte exemplo: Imagine que desejamos criar uma classe "Historico" que tem a capacidade de escrever linhas de texto através do método Escrever(string texto).
Este método irá receber o texto a ser escrito como parâmetro e escrever em 3 tipos de recipientes diferentes, de acordo com a implementação do escritor que ele contém. Os recipentes são: Console, Arquivo ou EventViewer
Existe uma dependência entre as classes "Historico" e "IEscritor", como podemos ver no código abaixo:
public class Historico
{
private IEscritor _escritor;
public Historico(IEscritor escritor)
{
_escritor = escritor;
}
public void Escrever(string msg)
{
_escritor.Write(msg);
}
}
public interface IEscritor
{
void Write(string msg);
}
Temos abaixo as classes que implementam a interface IEscritor, cada uma escreve em um tipo diferente de recipiente.
public class ConsoleWriter : IEscritor
{
public void Write(string msg)
{
Console.WriteLine(msg);
Console.ReadLine();
}
}
public class FileWriter : IEscritor
{
public void Write(string msg)
{
using (StreamWriter streamWriter =
new StreamWriter("c:\\TestUnity.txt",true))
{
streamWriter.WriteLine(msg);
}
}
}
public class EventViewerWriter : IEscritor
{
public void Write(string msg)
{
EventLog.WriteEntry("TestUnity", msg,
EventLogEntryType.Information);
}
}
Repare que a classe "Historico" foi construída utilizando a abordagem Contructor Injection. Agora vamos modificá-la para usar o UAB.
public class Historico
{
private IEscritor _escritor;
[Dependency]
public IEscritor Escritor
{
get { return _escritor; }
set { _escritor = value; }
}
public void Escrever(string msg)
{
_escritor.Write(msg);
}
}
Como podemos ver, a estrutura ficou semelhante a que conhecemos por Setter Injection porém temos a adição do atributo [Dependency].
Este atributo indica para o container do UAB qual classes/interfaces deverão ser criadas de acordo com a sua configuração.
Supondo que temos o ponto de entrada do nosso programa descrito da seguinte maneira:
class Program
{
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
UnityConfigurationSection section =
(UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(container);
Historico _history = container.Resolve
_history.Escrever("Hello");
}
}
Este programa escreve o texto "Hello" em um arquivo, event viewer ou console de acordo com o que indica o objeto UnitConfigurationSection. Neste caso a configuração está no arquivo App.config , repare que a propiedade "mapTo" indica um escritor do tipo Console.
<configuration>
<configSections>
<section name="unity"
type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration" />
</configSections>
<unity>
<containers>
<container>
<types>
<type
type="MyApp.BLL.IEscritor,MyApp.BLL"
mapTo="MyApp.BLL.ConsoleWriter,
MyApp.BLL" />
</types>
</container>
</containers>
</unity>
</configuration>
A solução acima demonstra o padrão que conhecemos por injeção de dependência, que envolve três elementos básicos:
- Objeto que consome o serviço/componente do qual é dependente (Historico)
- Declaração das dependências do objeto definidas através de Interfaces (IEscritor)
- Injetor/Provedor/Container responsável por criar instâncias das classes que implementam as interfaces (ConsoleWriter, FileWriter e EventWriter)
A classe Historico descreve quais componentes ela depende para executar o seu trabalho. O container decide qual classe satisfaz os requerimentos do objeto Historico e gera um objeto desta classe para injetar no objeto.
A grande vantagem de delegar esta tarefa ao container é que ele consegue decidir em tempo de execução qual objeto satisfaz os requerimentos. Várias implementações de um único componente podem ser criados e injetados em um código de testes por exemplo. O código de teste pode testar todos os componentes sem se importar com como foram implementados.
Este padrão é muito usado para mockar objetos de teste e para localizar plugins componentes e serviços em tempo de execução.
No próximo post estarei abordando outra maneira de aplicar a Inversão de Controle, o padrão Service Locator.
Fontes:
http://msdn.microsoft.com
http://www.macoratti.net
http://www.martinfowler.com

No comments:
Post a Comment