C# - O padrão de projeto Null Object
 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 class SamsungGalaxy : ICelular
{
    public void Desliga()
    {
        Console.WriteLine("\nSamsung Galaxy DESLIGADO!");
    }

    public void Liga()
    {
        Console.WriteLine("\nSamsung Galaxy LIGADO!");
    }
}

//Nossa classe Null implementa a interface como um Singleton 
public class NullCelular : ICelular
{
    private static NullCelular _instance;
    private NullCelular()
    { }

    public static NullCelular Instance
    {
        get
        {
            if (_instance == null)
                return new NullCelular();
            return _instance;
        }
    }

    //métodos que não fazem nada
    public void Desliga()
    { }

    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
{
    public ICelular GetCelularPorNome(string nomeCelular)
    {
        ICelular celular = NullCelular.Instance;

        switch (nomeCelular)
        {
            case "sony":
                celular = new SonyXperia();
                break;

            case "apple":
                celular = new AppleIPhone();
                break;

            case "samsung":
                celular = new SamsungGalaxy();
                break;
        }
        return celular;
    }
}

 


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
 

Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6
Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6

Referências:


José Carlos Macoratti