Alguns fatos preliminares:
- Se você ainda não sabe muito bem o que é “injeção de dependência”, bem, você pode até não saber, mas já utilizou esse “conceito” em algum momento da sua vida, nem que seja apenas na acadêmica. Acredite!
- O nome “Injeção de dependência” pode ser assustador, em essência, pelo simples fato de citar “injeção” logo de prima. Um leve arrepio nos acomete! :P
- É mais simples do que você imagina!
- O conceito é agnóstico à linguagem, ou seja, pode ser aplicado à qualquer linguagem que implemente o paradigma de orientação a objetos.
Para quem esse artigo foi escrito?
O ideal é que você tenha ao menos conhecimento básico em orientação a objetos. Apesar dos exemplos terem sido escritos em PHP, não é difícil o paralelo com qualquer outra linguagem.
Curso Zend Expressive - Microframework PHP
Conhecer o cursoO que é uma dependência?
Uma dependência é simplesmente um objeto que a sua classe precisa para funcionar.
A classe UserCommand
usa o objeto Notification
. Podemos dizer que a classe UserCommand
tem como dependência um objeto do tipo Notification
.
Como injeto uma dependência?
A dependência é injetada à classe que a utiliza, ou seja, obrigatoriamente ela precisa ser externa a essa classe.
Isso quer dizer que, para que haja uma injeção de dependência, a instanciação de um objeto não deve se dar dentro da classe, mas do lado de fora dela e então, injetada.
Vamos a um exemplo prático para clarear um pouco as coisas?
<?php
class Notification
{
public function send()
{
// TODO
}
}
class UserCommand
{
public function handle()
{
$notification = new Notification();
$notification->send();
}
}
Temos uma classe UserCommand
que no método handle()
precisa instanciar um objeto Notification
para, por exemplo, enviar uma notificação por e-mail de que o comando foi executado. O objeto é instanciado ali mesmo dentro do método, embutido na classe.
Na injeção de dependência você injeta o objeto (a dependência) na classe através de seu método construtor ou através de um método setter.
Veja o nosso caso de uso anterior, mas reescrito para receber a dependência ao invés de instanciá-la diretamente na classe:
<?php
class Notification
{
public function send()
{
// TODO
}
}
class UserCommand
{
protected $notification;
public function __construct(Notification $notification)
{
$this->notification = $notification;
}
public function handle()
{
$this->notification->send();
}
}
__construct()
é como se declara um método construtor em PHP, diferente de linguagens como Java onde se utiliza o mesmo nome da classe, por exemplo:public UserCommand()
.
Note que agora recebemos a dependência pelo construtor da classe UserCommand
. Isso quer dizer que, na instanciação dela, temos que “injetar” o objeto dependência:
$notification = new Notification();
$command = new UserCommand($notification);
$command->handle();
O objeto Notification
foi “injetado” no construtor da classe UserCommand
. Isso é tudo o que significa injeção de dependência. Viu como é simples?
Curso Doctrine ORM - Fundamentos
Conhecer o cursoOk, mas por que eu usaria injeção de dependência?
Com a injeção de dependência diminuímos o acoplamento das nossas classes. E a razão para isso ser tão importante está no princípio da inversão de dependência em que o código deve depender de abstrações e não de implementações concretas.
No PHP e em outras linguagens, “depender de abstrações” significa depender de Interfaces. Quando uma classe depende de uma interface ela pode receber qualquer dependência que satisfaça aquele “contrato” (interface).
Como podemos aplicar o princípio da inversão de dependência no nosso exemplo? Primeiramente observe que estamos recebendo no construtor uma implementação concreta:
public function __construct(Notification $notification)
Só estamos aceitando objetos do tipo Notification
. Todo o resto não é aceito. Estrito.
Para nos adequarmos ao conceito, podemos então criar uma Interface e começar a depender dela:
<?php
interface NotificationInterface
{
public function send();
}
class Notification implements NotificationInterface
{
public function send()
{
// TODO
}
}
class HipChatNotification implements NotificationInterface
{
public function send()
{
// TODO
}
}
class SlackNotification implements NotificationInterface
{
public function send()
{
// TODO
}
}
class UserCommand
{
protected $notification;
public function __construct(NotificationInterface $notification)
{
$this->notification = $notification;
}
public function handle()
{
$this->notification->send();
}
}
Criamos uma Interface NotificationInterface
e agora temos três classes para notificação:
- Notification
- HipChatNotification
- SlackNotification
E essas três classes implementam a interface que criamos:
class Notification implements NotificationInterface
class HipChatNotification implements NotificationInterface
class SlackNotification implements NotificationInterface
Quando uma classe implementa uma Interface ela está firmando um “contrato”, ela está dizendo que vai seguir as “regras”, ou seja, ela é obrigada a implementar os métodos assinados na interface. No nosso exemplo, a nossa interface assina apenas o método send()
:
interface NotificationInterface
{
public function send();
}
Agora, na prática, a nossa classe UserCommand
não se importa se vai receber como dependência um objeto Notification
, HipChatNotification
ou SlackNotification
, ela se importa em receber um objeto de uma classe que implemente a interface NotificationInterface
. Ou seja, ela está alheia à implementação concreta.
Se quisermos enviar uma notificação para um canal do Slack ao executar a classe comando:
$notification = new SlackNotification();
$command = new UserCommand($notification);
$command->handle();
Ou, se quisermos enviar para o HipChat:
$notification = new HipChatNotification();
$command = new UserCommand($notification);
$command->handle();
Percebeu que trocamos a forma de notificar sem nem tocar na classe UserCommand
?
Palavras finais
Injeção de dependência é um princípio, uma boa prática, independe de linguagem e traz flexibilidade e manutenibilidade ao software. Não obstante, para quem desenvolve orientado a testes, sem injeção de dependência fica praticamente “impossível” desenvolver um software com boa testabilidade.
Como qualquer outro princípio ou padrão de projeto você não é obrigado a seguir. Eles são apenas caminhos, boas práticas para se resolver problemas comuns.
O que ler a seguir?
Recomendo a leitura do artigo container de injeção de dependência que está intimamente atrelado ao que acabamos de estudar. :)