Neste artigo vou apresentar o padrão de projeto Null Object. |
O padrão Null Object é um dos padrões de projeto
mais comuns - e é um dos mais fáceis de compreender e implementar.
Sua finalidade é permitir que você evite lançar acidentalmente um Null ReferenceException e uma infinidade de código de verificação de objetos nulos.
A seguir temos o diagrama de classe UML deste padrão:
Em grande parte do código que escrevemos, independentemente da linguagem de
programação, sempre temos que realizar verificações como a seguinte :
if (valor
== null) return; //código |
Acabamos adicionando tantas verificações para nulos em nossos métodos que pode
ficar difícil descobrir qual o objetivo do método. Isso faz com que nosso código
se torne difícil de ler e de entender.
Cenário
Vamos considerar um cenário hipotético onde temos uma aplicação Console que usa um tipo ICelular para realizar algumas operações.
O tipo ICelular é retornado de um CelularRepository usando um método chamado GetCelularPorNome(nomeCelular), no qual nomeCelular é o nome do celular no qual desejamos operar.
Um código implementando o requisito acima seria parecido com o seguinte :
class Program
{
static void Main(string[] args)
{
var celularRepository = new CelularRepository();
ICelular celular = celularRepository.GetCelularByNome("sony");
Celular.Liga();
Celular.Desliga();
}
}
|
Neste código, como não estamos realizando uma verificação para nulos para o objeto retornado por CelularRepository, podemos obter uma NullreferenceException para alguma entrada inválida para o método GetCelularPorNome. Nesse caso, podemos resolver esse problema simplesmente introduzindo uma verificação nula para celular : if(celular == null)
Mas seria esta realmente uma boa solução ?
E se o GetCelularPorNome estiver sendo usado em vários métodos ?
Seria viável adicionar uma verificação para nulos em todos os métodos?
Neste cenário é que entra em cena o padrão Null Object.
O padrão Null Object : implementação
O papel do padrão Null Object e fazer com a lógica do programa se livre de realizar verificações nulas sempre que possível.
Todos nós sabemos que não podemos chamar métodos em uma referência nula, pois isso resulta em uma exception do tipo NullReferenceException. Este padrão fornece um objeto não funcional no lugar de uma referência nula e, portanto, permite que métodos sejam chamados nele.
Portanto, para alguma entrada inválida para o método GetCelularPorNome, nosso CelularRepository retornaria um objeto ICelular instanciado, mas nulo, no lugar de uma referência nula. Diferente de qualquer outro objeto Celular, quando realizamos qualquer operação neste objeto, nada acontece.
Em nosso cenário, temos que implementar o método GetCelularPorNome de forma que ele retorne um objeto ICelular nulo no lugar de uma referência nula, para qualquer entrada inválida.
Para conseguir isso, primeiro precisamos criar uma classe de implementação da interface ICelular. O objeto desta classe seria nossa implementação de objeto nulo.
A seguir temos o código que podemos usar para implementar o padrão Null Object para este cenário:
public interface ICelular { void Liga(); void Desliga(); } //Tipo de celular implementando a interface ICelular public void Liga() //Nossa classe Null implementa a interface como um Singleton
public static NullCelular Instance //métodos que não fazem nada public void Liga() |
Nosso repositório pode retornar diferentes tipos de celulares como SamsungGalaxy, AppleIPhone e SonyXperia. Todos os tipos implementam a interface ICelular e a nossa classe de objeto nulo, NullCelular, é um Singleton clássico e é implementada a partir da interface ICelular.
Podemos ver no código que, a classe
NullCelular como outros tipos, implementa os métodos
Ligado e Desligado, entretanto, as implementações destes métodos não fazem
nada.
Agora que temos nosso objeto nulo pronto, é hora de usá-lo no lugar certo. Não
queremos ter nenhuma verificação nula para o tipo retornado do repositório na
lógica principal. Portanto, nosso CelularRepository
deve retornar uma instância NullCelular no lugar de
uma referência nula para qualquer entrada inválida.
Vamos dar uma olhada em como isso funciona no código.
public class CelularRepository
case "apple":
case "samsung":
|
Primeiro criamos uma variável do tipo ICelular e a
inicializamos com uma instância de NullCelular.
Se o nomeCelular não corresponder a um caso na instrução switch, o objeto NullCelular será retornado pelo método GetCelularPorNome. Em nossa lógica principal, quando tentamos chamar métodos neste objeto, não obteremos nenhuma exceção e, portanto, o fluxo de trabalho não será interrompido. No entanto, os métodos não farão nada.
Considerações :
O padrão Null Object nos ajuda a escrever um código limpo evitando verificações nulas sempre que possível
Usando este padrão, os chamadores não precisam se
preocupar se têm um objeto nulo ou um objeto real.
Não é possível implementar o padrão em todos os cenários. Às vezes, é provável
que retorne uma referência nula e execute algumas verificações nulas.
No exemplo de código acima, estamos usando um objeto nulo implementando uma
interface. No entanto, podemos ter uma abordagem bastante semelhante com a
classe base abstrata também.
O objeto nulo geralmente é um singleton. No entanto, às vezes queremos que nosso
estado de objeto varie nas instâncias.
Este padrão é útil em situações em que queremos retornar um objeto do tipo
esperado, mas não fazemos nada.
É importante observar que, a menos que os desenvolvedores estejam cientes de que
a implementação do objeto nulo existe, eles ainda podem fazer verificações
nulas.
E estamos conversados...
"Porque não nos
chamou Deus para a imundícia, mas para a santificação"
1 Tessalonicenses 4:7
Referências: