Mostrar mensagens com a etiqueta Dependency Injection. Mostrar todas as mensagens
Mostrar mensagens com a etiqueta Dependency Injection. Mostrar todas as mensagens

sábado, 5 de julho de 2014

Compreender a “Dependency Injection” (II)

Introdução

Esta é a segunda parte de uma série de artigos dedicados ao tema da Dependency Injection (Injecção de Dependência).

Parte I – Apresentação e Conceitos Base
Parte II – Introdução prática da DI (este artigo)
Parte III – Boa programação da DI
Parte IV – Contentores IoC – Exemplos de Aplicação

Depois de termos feito uma apresentação teórica dos conceitos base, neste artigo vamos iniciar a exploração da DI. Começamos com um exemplo simples que levanta uma série de questões relacionadas com as dependências do código e depois vamos analisando, passo-a-passo, a evolução das soluções para este problema, até introduzirmos o conceito de DI. 

Conteúdo

O problema do acoplamento

Vamos analisar um exemplo muito simples para percebermos esta questão do acoplamento do código e por que razão o alto acoplamento representa um problema.

No nosso exemplo, necessitamos criar uma classe que represente uma pessoa, classe essa que deve implementar uma funcionalidade que permita à pessoa cumprimentar um amigo, através do envio de um email.

public class Pessoa
{
    public void CumprimentaAmigo()
    {
        // cdigo com mecanismo para enviar email
    }
}

Esta primeira versão da nossa classe não respeita o Princípio da Responsabilidade Única. A nossa classe, para além da responsabilidade de implementar as funcionalidades relacionadas com a entidade Pessoa, também tem a responsabilidade de implementar um mecanismo de envio de email.

Para resolvermos esta questão, vamos separar estas responsabilidades em duas classes distintas:

public class ServicoEmail
{
    public void EnviaEmail(string assunto, string msg)
    {
        // cdigo com mecanismo para enviar email
    }
}
public class Pessoa
{
    private ServicoEmail email = new ServicoEmail();
    public void CumprimentaAmigo()
    {
        email.EnviaEmail("Ola", "Ola amigo, como vai isso?");
    }
}

E pronto! Resolvemos o problema do Princípio da Responsabilidade Única. Criámos uma nova classe ServicoEmail que trata do envio das mensagens de Email e a nossa classe Pessoa cria uma instância dessa classe para implementar  correctamente o método CumprimentaAmigo.

Todos concordamos que este é um exemplo muito simples e directo, mas que enferma de algumas limitações, conforme passamos a descrever:

  • A classe Pessoa depende da classe ServicoEmail. Existe uma forte conexão (alto acoplamento) entre estas duas classes.
  • Vamos imaginar que temos uma nova versão melhorada da classe de envio de email, ServicoEmailRapido. A única forma de utilizarmos esta nova classe será alterarmos a classe Pessoa.
  • Digamos que resolvemos inserir um parâmetro no construtor da classe ServicoEmail. Mais uma vez, somos obrigados a alterar a classe Pessoa.
  • Por uma decisão de design, a classe ServicoEmail passa a ser singleton. Lá temos que alterar a classe Pessoa…
  • De forma a melhorar o sistema de notificações, decide-se criar novos sistemas de envio de mensagens, como SMS ou Twiter. A classe Pessoa tem que ser modificada para poder usar estas novas implementações.
  • Outro programador necessita utilizar a classe Pessoa, mas quer usar outro sistema de mensagens. Isto não pode ser feito com a versão actual da classe Pessoa, porque está agarrada à classe ServicoEmail. O que acontece normalmente é este programador duplicar a classe Pessoa e fazer as alterações de que necessita. O projecto acaba com duas versões da classe Pessoa.
  • Temos estado a analisar cenários em que ocorrem alterações de código. Todas as alterações devem ser testadas. Como podemos testar a classe Pessoa sem incluir a classe ServicoEmail? Como podemos criar testes unitários automatizados (com NUnit, p.ex.), neste caso?

Essas limitações podem ser melhoradas se alterarmos a nossa forma de pensar e recriarmos o código de uma forma mais modular. Isto é importante, mas é independente da DI, como veremos na próxima secção.

Programar baseado em Abstrações

O que pretendemos é eliminar a dependência entre a classe Pessoa e a classe ServicoEmail, que está na origem das limitações atrás descritas. Esta dependência está expressa na linha de código:

    private ServicoEmail email = new ServicoEmail();

O que necessitamos é remover a referência explícita à classe ServicoEmail, que causa a dependência. Fazendo uma pequena análise a esta situação, o que a classe Pessoa necessita, no método CumprimentaAmigo, é de um serviço genérico, que lhe permita enviar mensagens. Estamos aqui a introduzir um conceito de abstração, que pode ser implementado através da criação e utilização dum Interface.

A alteração da forma de pensar mencionada na secção anterior, passa então por aplicar o Princípio da Abstração, através da utilização de interfaces. No entanto, muitos programadores não usam interfaces porque os veem como código adicional, não necessário. Como em tudo, temos de ver as coisas dentro do contexto e haverá casos em que não necessitamos de interfaces (o caso do “Hello World”, p.ex.). No entanto, a programação com interfaces permite produzir um código muito mais modular e extensível, como ilustrado nos exemplos a seguir. Esta abordagem também melhora a testabilidade. O exemplo discutido na seção anterior foi bastante simples, mas incluiu várias armadilhas que podem ser facilmente evitadas se usarmos interfaces em vez de classes concretas para definir os serviços.

A correcta utilização dos interfaces envolve as três etapas seguintes:

1. Definir o Interface

Começamos por definir o interface IServicoMensagens que inclui a definição da assinatura do método EnviaMensagem.

public interface IServicoMensagens
{
    void EnviaMenssagem(string assunto, string msg);
}

2. Implementar o Interface
Na nossa lista de limitações, mencionámos várias formas de enviar mensagens. Vamos criar uma classe para cada uma delas, que implementa o novo interface.

public class ServicoEmail : IServicoMensagens
{
    public void EnviaMenssagem(string assunto, string msg)
    {
        // cdigo com mecanismo para enviar email
    }
}
public class ServicoEmailRapido : IServicoMensagens
{
    public void EnviaMenssagem(string assunto, string msg)
    {
        // cdigo com mecanismo para enviar email
    }
}

public class ServicoSms : IServicoMensagens
{
    public void EnviaMenssagem(string assunto, string msg)
    {
        // cdigo com mecanismo para enviar sms
    }
}

public class ServicoTwiter : IServicoMensagens
{
    public void EnviaMenssagem(string assunto, string msg)
    {
        // cdigo com mecanismo para enviar tweet
    }
}

3. Usar o Interface
Finalmente, em vez de usarmos classes, utilizamos interfaces. Na classe Pessoa, substituímos o campo email pelo o interface IServicoMensagens conforme indicado abaixo.

public class Pessoa
{
    private IServicoMensagens servicoMsg;

    public void CumprimentaAmigo()
    {
        servicoMsg.EnviaMenssagem("Ola", "Ola amigo, como vai isso?");
    }
}

Com a introdução do interface, criámos um nível de abstração que nos permitiu remover a dependência da classe Pessoa sobre as classes de serviço de envio de mensagens.

Todavia, o código acima ainda tem um problema. Declarámos uma variável que representa o serviço, mas esse serviço nunca é instanciado:

    private IServicoMensagens servicoMsg;

Como não podemos instanciar serviços directamente, temos que instanciar uma classe. Mas se instanciarmos uma classe, voltamos a criar uma dependência, como antes:

    private IServicoMensagens servicoMsg = new ServicoEmail();

Vamos então recapitular as questões que nos surgiram:

  • Ao referenciarmos directamente as classes de serviço, criámos uma dependência.
  • Removemos a dependência, utilizando um interface para a declaração da variável do serviço.
  • Mas não podemos instanciar interfaces, temos sempre que instanciar uma classe. Todavia, se instanciarmos uma classe, voltamos a ter a dependência.

Vamos fazer uma pequena modificação no código da classe Pessoa, que nos permita ultrapassar estas duas questões: remover a dependência e ter um objecto instanciado que implemente o interface do serviço.

public class Pessoa
{
    private IServicoMensagens servicoMsg;

    public Pessoa(IServicoMensagens servico)
    {
        this.servicoMsg = servico;
    }

    public void CumprimentaAmigo()
    {
        servicoMsg.EnviaMenssagem("Ola", "Ola amigo, como vai isso?");
    }
}

Note-se que a classe Pessoa não está a inicializar o serviço, mas espera por ele como um parâmetro do seu construtor. Este é um elemento-chave no design, que melhora a modularidade, extensibilidade e testabilidade. A classe Pessoa não é dependente de qualquer implementação, mas apenas de um serviço definido por um interface. Isso significa que podemos usar a classe Pessoa, sem nos preocuparmos com a implementação subjacente do serviço de mensagens. Além disso, diferentes instâncias da classe Pessoa podem ser criadas utilizando diferentes serviços de mensagens.

Vamos então criar uma pequena classe que instancie objectos da classe Pessoa e que injecte as dependências nos respectivos construtores.

public class Sistema
{
    public void main()
    {
        IServicoMensagens servico = new ServicoEmail();
        Pessoa socio = new Pessoa(servico);
        socio.CumprimentaAmigo();
    }
}

Acabámos de introduzir o conceito de Dependency Injection. Uma classe não depende directamente de outras classes, mas apenas de abstrações, representadas por interfaces. Os objectos com instâncias concretas que implementam os interfaces, são “injectados” em runtime. No exemplo anterior, estamos a injectar no construtor, mas esta não é a única forma de realizar a DI.

Na abordagem inicial, a classe definia exactamente e controlava as suas dependências. As instruções para instanciar as dependências estavam na própria classe. Com esta abordagem, não é a classe que decide quem são os objectos que implementam as suas dependências, mas terá que ser outra entidade a tomar essa decisão antes de instanciar a própria classe. Temos assim o conceito de Inversion of Control (IoC).

A ajuda da DI

Podemos facilmente verificar que a DI nos permite resolver as limitações indicadas nas secções anteriores. Com a injecção da dependência no construtor da classe Pessoa, esta deixa de ser afectada por qualquer alteração nas classes dos serviços (desde que não se altere o interface, claro). A questão de podermos ter diferentes instâncias da classe Pessoa a usarem diferentes serviços, também é fácil de resolver.

    public void Central()
    {
        ServicoEmail mail = new ServicoEmail();
        ServicoSms sms = new ServicoSms();
        ServicoTwiter tweet = new ServicoTwiter();

        List<Pessoa> socios = new List<Pessoa>();
        socios.Add(new Pessoa(mail));
        socios.Add(new Pessoa(sms));
        socios.Add(new Pessoa(tweet));
        socios.Add(new Pessoa(mail));

        foreach (Pessoa s in socios)
            s.CumprimentaAmigo();
    }

Começamos por criar três instâncias de serviços de tipos distintos, mas cada uma delas implementa o interface IServicoMensagens. Depois criamos uma lista de objectos da classe Pessoa e injectamos serviços diferentes em elementos diferentes. Finalmente percorremos todos os elementos da lista e executamos o método CumprimentaAmigo. Neste exemplo, o primeiro e o quarto sócios enviam uma mensagem de email, o segundo uma mensagem SMS e o terceiro uma mensagem do Twiter.

Queremos ainda fazer uma breve referência à questão dos testes unitários (serão o tema de outro artigo). Vamos imaginar que necessitamos testar se o método CumprimentaAmigo da classe Pessoa está correctamente implementado. Nem sempre é possível garantir toda a infraestrutura de envio de mensagens de Email, SMS ou Twiter. Normalmente isto estará presente no ambiente de produção, mas não nos computadores dos programadores. Então vejamos como a DI pode ajudar os nossos testes.

    public class ServicoFake: IServicoMensagens
    {
        public string assunto;
        public string msg;

        public void EnviaMenssagem(string assunto, string msg)
        {
            this.assunto = assunto;
            this.msg = msg;
        }
    }

    [TestClass]
    public class TestesPessoa
    {
        [TestMethod]
        public void PessoaPodeCumprimentar()
        {
            // set
            ServicoFake servico = new ServicoFake();
            Pessoa pessoa = new Pessoa(servico);

            // act
            pessoa.CumprimentaAmigo();

            // assert
            Assert.AreEqual(servico.assunto, "Ola");
            Assert.AreEqual(servico.msg, "Ola amigo, como vai isso?");

        }
    }

Como a classe Pessoa só depende do interface IServicoMensagens, para a testarmos, basta criar um serviço mock ou fake, que implemente o interface, que vamos usar com valores pré-determinados, para validarmos o funcionamento do método CumprimentaAmigo.

Algumas conclusões

Acabámos de ver, através de pequenos exemplos, alguns dos benefícios da DI. Permite-nos uma clara separação de conceitos, a garantia de respeitarmos os princípios SOLID e a construção de código modular, extensível e facilmente testável.

Pode-se argumentar que a introdução da DI tornou a classe Pessoa mais complexa de instanciar uma vez que o construtor requer parâmetros. Este é um aspecto a ter conta, a DI acrescenta complexidade à solução. Já na introdução do primeiro artigo tínhamos mencionado que a DI em sistemas simples pode ser mais prejudicial do que benéfica. Há que analisar sempre o contexto, dimensão e complexidade do sistema.

De qualquer forma, hoje em dia existem várias frameworks de IoC que tornam a implementação e configuração da DI numa tarefa simples e directa. Vamos ver alguns exemplos de utilização destas frameworks noutro artigo.

 

Notas:

- Neste artigo optou-se por explorar o tema da DI através de pequenos exemplos de código, muito simples, cujo objectivo é apenas fazer uma introdução e aguçar o apetite do(a) leitor(a).

- Os exemplos de código apresentados foram desenvolvidos em linguagem C#. Poderiam estar em C++, Java, VB.NET ou qualquer outra linguagem OO. a opção pelo C# deveu-se simplesmente a uma maior facilidade pessoal e abrangência em termos de documentação disponível para consulta.

- Ainda nos exemplos, optou-se por usar uma nomenclatura de classes, métodos e variáveis em língua Portuguesa, para melhor clarificação e compreensão do tema. Convenções e regras de nomenclatura recomendadas serão tema de outro artigo.

segunda-feira, 30 de junho de 2014

Os Princípios S.O.L.I.D. da POO

Introdução

Hoje em dia, quando falamos de programação de software, inevitavelmente estamos a falar de Programação Orientada a Objectos (POO). Torna-se pertinente perguntar o que é o design orientado a objectos (OO)? É sobre o quê? Quais os seus benefícios? Quais os seus custos? Apesar de virtualmente todos os programadores de software utilizarem uma linguagem Orientada a Objectos (OO), de alguma forma, muitos de nós utiliza estas linguagens sem saber porquê ou sem saber como retirar o máximo benefício delas.

De todas as revoluções que ocorreram na indústria do desenvolvimento de software, duas tiveram tanto sucesso que moldaram a nossa mentalidade de modo que as tomamos como um dado adquirido: Programação Estruturada e Programação Orientada a Objectos. Todas as principais linguagens de programação que usamos hoje em dia são fortemente influenciadas por estas duas disciplinas.

Mas, o que é necessário para fazermos Programação Orientada a Objectos?

Bem, podemos começar por dizer que temos que usar uma linguagem Orientada a Objectos (OO), como, por exemplo, Java, C#, C++, VB.NET. Mas só porque estamos a usar uma linguagem OO, só porque estamos a programar com classes, não quer dizer que estejamos a fazer POO. Ou pelo menos, não quer dizer que o estejamos a fazer da forma correcta. Muitas vezes os programadores não têm conhecimento dos princípios e fundamentos que são a base das disciplinas de que a linguagem de programação que estão a usar derivou.

Este artigo é assim sobre as bases da POO, sobre os Princípios da Programação Orientada a Objectos. Em específico, vamos analisar os princípios S.O.L.I.D., definidos por Robert C. Martin.

Conteúdo

Estes princípios focam-se nos aspectos de gestão de dependências da POO, i.e. a gestão do Acoplamento entre os módulos do software. O alto acoplamento é um problema que a maioria de nós já enfrentou. Sempre que temos que analisar algum pedaço de código legacy, somos confrontados com má gestão de dependências, resultando em software que é difícil de alterar, frágil e não reutilizável. Por outro lado, quando as dependências são bem geridas, resulta código com baixo acoplamento e o software é flexível, robusto e reutilizável. Então a gestão de dependências e este princípios, estão na fundação das características e facilidades que os programadores pretendem que o software tenha.

The Single Responsability Principle (Princípio da Responsabilidade Única)

Uma classe deve ter uma e apenas uma razão para mudar. Basicamente, isto significa que cada classe deve ter uma única responsabilidade e que a responsabilidade deve estar totalmente encapsulada pela classe. Todos os seus serviços devem estar estreitamente alinhados com essa responsabilidade.

Por exemplo, se criarmos uma classe que represente uma Encomenda, não queremos que essa classe faça a persistência dos dados para BD e que os exporte também para XML. Porquê? Porque se, mais tarde, quisermos mudar de BD ou alterar o esquema do XML, estamos a possibilitar que a alteração de uma responsabilidade obrigue à alteração de outra responsabilidade.

The Open/Closed Principle (Princípio do Aberto/Fechado)

As entidades de software (classes, módulos, funções, etc.) devem estar abertas para extensão, mas fechadas para modificação ou, dito de outra maneira, devemos ser capazes de estender o comportamento de uma classe, sem a modificar.

Em princípio, isto parece contraditório: como podemos fazer um objeto comportar-se de uma forma diferente  sem o modificar? A resposta: usando abstrações, ou colocando o comportamento (responsabilidade) em classes derivadas. Por outras palavras, ao criarmos classes base com funções “overridable” (que permitem substituição), podemos criar novas classes derivadas, que fazem a mesma coisa de forma diferente, sem alterar a funcionalidade base. Mais, se as propriedades da classe abstracta necessitam ser comparadas ou organizadas em conjunto, uma outra abstração deve lidar com isso. Esta é a base do argumento "manter privadas todas as variáveis ​​do objeto" (Encapsulamento).

The Liskov Substitution Principle (Princípio da Substituição de Liskov)

As classes derivadas devem ser substituíveis pelas suas respectivas classes base. Por outras palavras, métodos que usem referências a classes base, têm que ser capazes de usar objectos de classes derivadas, sem o saber.

Quando se faz a sobreposição de um método duma classe abstracta, este tem que ser implementado de forma correcta, na classe derivada. Ou, “quando se usa um objecto através do interface da sua classe base, o objecto derivado não deve esperar que essa utilização obedeça a pré-condições que sejam mais fortes do que aquelas requeridas na classe base". A ilustração sempre popular desta caraterística é o exemplo quadrado-rectângulo.

Nota: este princípio deve o seu nome a Barbara Liskov, que o enunciou inicialmente numa conferência em 1987.

The Interface Segregation Principle (Princípio da Segregação de Interfaces)

Devem-se refinar e criar interfaces específicos para cada cliente, ou, dito de outra forma, os clientes não devem ser forçados a depender de interfaces que não usam. Quando um cliente depende de uma classe que implementa interfaces que o cliente não usa, mas que são usados por outros clientes, então esse cliente vai ser afectado pelas mudanças que os outros clientes possam forçar na classe.

The Dependency Inversion Principle (Princípio da Inversão da Dependência)

Devemos depender de abstrações e não de concretizações. Abstrações não devem depender de detalhes. Os detalhes é que devem depender de abstrações. Isto está intimamente relacionado com o Princípio Aberto/Fechado, discutido anteriormente. Ao passar dependências (tais como conectores a dispositivos de armazenamento) para as classes, como abstrações, removemos a necessidade de programar para uma dependência específica.

A utilização de Dependency Injection é uma forma de seguir este princípio.

Este  artigos apresenta apenas uma breve introdução os princípios SOLID, pretendendo destacar a utilidade e vantagens da sua aplicação. Para um conhecimento mais aprofundado sobre o tema, recomenda-se a pesquisa e leitura da extensa bibliografia online.

quinta-feira, 26 de junho de 2014

Compreender a “Dependency Injection” (I)

Introdução

Ao ler documentos, artigos ou livros sobre programação, com certeza já se deparou com o termo Dependency Injection (Injecção de Dependência).

Mas o que é exactamente Dependency Injection (DI)? E porque queremos usar DI?

Acontece que a DI é um pattern extremamente útil para o desenvolvimento de aplicações, com algum grau de complexidade. Em pequenas aplicações, o uso da DI tem um benefício reduzido, podendo, mesmo, criar complexidade desnecessária.

O principal resultado da utilização de DI é a obtenção de um sistema com Baixo Acoplamento (loose coupling), o que resulta em benefícios em termos de extensibilidade, testabilidade e late binding.

Compreender a Dependency Injection

Vamos iniciar uma série de artigos que nos ajudarão a compreender o que é a DI e os seus benefícios e veremos alguns exemplos de aplicação.

Parte I – Apresentação e Conceitos Base (este artigo)
Parte II – Introdução prática da DI
Parte III – Boa programação da DI
Parte IV – Contentores IoC – Exemplos de Aplicação

Nesta primeira parte, vamos começar por especificar alguns dos conceitos base relacionados com a DI.

Na segunda parte, através da análise de exemplos simples, vamos motivar a introdução do conceito de DI e verificar alguns benefícios da sua utilização. Na primeira parte, temos uma descrição mais teórica dos conceitos, na segunda, vamos introduzir a DI na prática.

Na terceira parte, vamos analisar algumas armadilhas quando se tenta gerir dependências. Vamos verificar como o desenvolvimento convencional nos pode deixar com código fortemente acoplado (mesmo quando pensamos que temos uma boa separação de conceitos). Vamos ver então como a adição da DI permite resolver este problema.

Finalmente, vamos apresentar alguns Contentores de IoC, com exemplos de configuração e aplicação.

Ao longo dos artigos serão feitas referências aos princípios S.O.L.I.D., que consistem num conjunto de 5 princípios da Programação Orientada a Objectos (POO), definidos por Robert C. Martin. Não entraremos em detalhes sobre estes princípios, sendo isso tratado futuramente noutro artigo.

Esta série de artigos apresenta apenas uma pequena introdução à DI, pretendendo destacar a utilidade e vantagens da sua utilização, bem como alguns exemplos de aplicação. Para um conhecimento mais aprofundado sobre o tema, recomenda-se a pesquisa e leitura da extensa bibliografia online.

Conteúdo

O que é a Dependency Injection

Um dos problemas com a DI é que existem dezenas de definições diferentes, que provocam uma confusão que se alastra à terminologia, objectivos e mecanismo.

Uma definição possível é a seguinte:

A DI é um padrão de desenho de software que permite que a escolha de componentes seja feita em run-time, ao invés de compile-time.

Infelizmente, esta definição, embora formalmente correcta, está um pouco incompleta. Menciona decisões em run-time (também chamadas de late binding), mas a DI é muito mais do que isso.

Vejamos outra definição:

Dependency Injection é um conjunto de princípios e padrões de desenho de software que nos permitem desenvolver código com Baixo Acoplamento.
Seemann, Mark, Dependency Injection in .NET, Manning, 2012

Esta definição é muito melhor. A DI tem tudo a ver com a criação de sistemas com baixo acoplamento, o que permite o late binding, entre outras coisas.

Então, o que pretendemos, nesta série de artigos, é ver como podemos criar código com Baixo Acoplamento.

Baixo Acoplamento, porquê?

Porque devemos criar código com baixo acoplamento?
Porque isso nos oferece uma preciosa ajuda numa quantidade de áreas. Eis algumas:

Extensibilidade
Extensibilidade representa a facilidade em adicionar novas funcionalidades ao código. A palavra "facilidade" significa que podemos fazer actualizações em locais específicos, sem que isso signifique ter que actualizar pedaços ao longo de todo o código base.

Late Binding
Conforme já mencionado, late binding é a capacidade de escolher quais os componentes que usamos em runtime, em vez de compile-time. Isto só é possível se o código tiver baixo acoplamento – o código só se preocupa com abstrações em vez de um tipo concreto em particular. Isso permite trocar componentes sem a necessidade de modificar o código.

Desenvolvimento Paralelo
Se o código tiver baixo acoplamento, torna-se mais fácil ter várias equipas de desenvolvimento a trabalhar no mesmo projeto. Podemos ter uma equipa a trabalhar na camada de negócio, e outra a trabalhar na camada de serviços. Como as camadas são independentes, as equipas estarão a trabalhar em código fonte diferente, que não afeta diretamente o outro.

Facilidade de Manutenção
Quando os componentes são independentes, a funcionalidade é isolada. Isto significa que se for necessário descobrir bugs ou ajustar a funcionalidade, sabemos exatamente onde procurar.

Testabilidade
Os Testes Unitários (Unit Testing) são um tema extremamente importante. O principal objetivo é testar pequenas unidades de código em isolamento. Quando temos código com baixo acoplamento, podemos facilmente criar dependências simuladas (mock/fake), de modo que podemos facilmente isolar as partes do código que realmente desejamos testar.

Inversion of Control (IoC)

Muitas pessoas referem-se a DI como Inversion of Control (Inversão de Controlo). Estes dois termos são por vezes usados ​​como sinónimos, mas DI é um subconjunto de IoC. Cabe aqui fazer uma pequena desambiguação entre os conceitos DI e IoC.

O termo Inversion of Control, originalmente, significava qualquer estilo de programação onde uma framework global ou o próprio runtime controlava o workflow de execução da aplicação. Isto era o oposto do que acontecia na programação procedimental.

Quando desenvolvemos uma aplicação web, seguimos o lifecycle das páginas ASP.NET, mas não estamos no controlo, a framework do ASP.NET é que está. Quando desenvolvemos um serviço WCF, podemos estar a escrever o código do serviço, mas não estamos no controlo, a framework WCF é que está.

Hoje em dia estamos tão habituados a utilizar frameworks (muito comuns na programação .NET, ou Java), que deixou de se dar um significado especial, mas não é a mesma coisa que ter o controlo total da execução, como acontece, por exemplo, numa aplicação de linha de comandos, mesmo no mundo .NET.

Antes da DI ter um nome definido, era comum chamar-se às frameworks que geriam dependências como Inversion of Control Containers e rapidamente o termo IoC passou a subentender-se como inversão de controlo sobre dependências. Mais tarde Martin Fowler introduziu o termo Dependency Injection para designar especificamente IoC no contexto de gestão de dependências. Desde então, DI tem sido aceite como a terminologia mais correcta.

Em resumo, IoC é um termo mais abrangente, que inclui, mas não está limitado à DI.

Padrões de desenvolvimento de Dependency Injection

A implementação da DI pode ser executada recorrendo a um variado número de design patterns:

  • Contructor Injection
  • Property Injection
  • Method Injection
  • Ambient Context
  • Service Locator

Nesta série de artigos vamos olhar em especial para a Constructor Injection, que é o principal padrão utilizado. O leitor poderá debruçar-se sobre os outros padrões, quando se sentir mais confortável com a DI.

Isto pode parecer um pouco complexo e poderá estar a equacionar se realmente vale a pena envolver-se na DI. Na realidade, estes padrões e princípios não são assim tão complicados. Vamos efectuar calmamente o nosso caminho, de modo a termos uma boa ideia do que vai acontecendo.

No próximo artigo: Compreender a “Dependency Injection” (II), as coisas vão ficar um pouco mais claras, quando começarmos a criar componentes e a aplicar a DI, verificando as suas vantagens.