Padrão de Projeto: Objeto Nulo

Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação. Você receberá um email de confirmação. Somente depois de confirmar é que poderei lhe enviar o conteúdo exclusivo por email.

Email inválido.
Blog /Android /Padrão de Projeto: Objeto Nulo

Padrão de Projeto: Objeto Nulo

Vinícius Thiengo14/07/2016
(915) (13) (1)
Go-ahead
"Dar o seu melhor neste exato momento vai colocá-lo no melhor lugar possível no momento seguinte."
Oprah Winfrey
Código limpo
Capa do livro Refatorando Para Programas Limpos
TítuloRefatorando Para Programas Limpos
CategoriaEngenharia de Software
AutorVinícius Thiengo
Edição
Ano2017
Capítulos46
Páginas598
Comprar Livro
Conteúdo Exclusivo
Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação.
Email inválido

Opa, blz?

Nesse artigo vamos abordar o padrão de projeto Objeto Nulo (ou Null Object) que é simples e em algumas linguagens já tem suporte nativo, Java está entre essas linguagens.

Esse padrão é também pré-requisito para entendimento do método de refatoração Introduzir Objeto Nulo.

Tópicos presentes no artigo:

Apresentação

O padrão Objeto Nulo permite que possamos trabalhar nossos códigos utilizando objetos que não têm ação (vazios) ou têm ações padrões, com isso sempre teremos um objeto e então a não utilização da verificação do valor null em trechos de código onde a lógica não precisa trabalhar com valores null.

O que? Em trechos de código?

Sim, no decorrer do conteúdo vamos comentar sobre algumas situações onde o valor null é realmente desejado.

Diagrama

Abaixo o diagrama de um projeto que pode estar trabalhando com o valor null, mais precisamente com a verificação if( obj != null ):

E então o diagrama do mesmo projeto, porém utilizando um objeto nulo:

Com o novo modelo de código, ao menos o trecho do projeto representado pelo diagrama acima, esse, dispensa verificação de segurança no código, pois o objeto nulo é um objeto do tipo esperado.

Código de exemplo

Antes de utilizar o padrão proposto precisamos entender o problema que é ter o tipo de código abaixo:

...
public boolean trocaCambio(){
if( carro.getInjecaoEletronica() != null ){
/* TODO */
}
return false;
}
...

 

As condicionais de verificação de null, quando espalhadas por todo o projeto, têm o sério problema de inflá-lo caso o trabalho com um objeto vazio ou com valores padrões seja possível.

O problema fica um pouco mais crítico quando se tem chamadas de métodos encadeadas como a seguir:

...
public boolean trocaCambio(){
if( carro.getInjecaoEletronica() != null ){
Cambio[] cambios = carro.getInjecaoEletronica()
.getFabricante()
.getCambios();
/* TODO */
}
return false;
}
...

 

Nesse caso, um código seguro, com verificação de null, seria algo como:

...
public boolean trocaCambio(){
if( carro.getInjecaoEletronica() != null
&& carro.getInjecaoEletronica().getFabricante() != null
&& carro.getInjecaoEletronica().getFabricante().getCambios() != null ){

Cambio[] cambios = carro.getInjecaoEletronica()
.getFabricante()
.getCambios();
/* TODO */
}
return false;
}
...

 

O padrão Objeto Nulo tem o objetivo de remover esses códigos condicionais repetidos e então permitir o trabalho somente com a lógica de negócio sem esses blocos de seleção (outro nome para "condicionais").

Ou seja, com o objeto convencional preenchido corretamente (dados vindos de um banco de dados ou até mesmo do usuário por meio de um formulário, por exemplo) ou com a versão de Objeto Nulo dele, o código será o mesmo, sem trechos especiais para um tipo ou outro.

Note que o Objeto Nulo será útil caso ele não afete a saída do código para o sistema ou para o usuário cliente.

Caso a lógica sendo utilizada realmente precise de um objeto convencional preenchido corretamente ou um valor null para informar algo na saída, não remova a utilização de null, isso provocará saídas inconsistentes.

Agora provavelmente você deve estar se perguntando: como implementar um Objeto Nulo?

Temos três opções. Mas antes vamos dar uma olhada mais de perto em nossa classe Carro, InjecaoEletronica e logo depois na classe Fabricante. Assuma que em nosso código cliente não há problemas em termos um objeto vazio.

Começando com a classe Carro:

public class Carro {
private InjecaoEletronica injecaoEletronica;

public void setInjecaoEletronica(InjecaoEletronica injecaoEletronica) {
this.injecaoEletronica = injecaoEletronica;
}

public InjecaoEletronica getInjecaoEletronica() {
return injecaoEletronica;
}
}

 

Nesse código de exemplo vamos assumir que os métodos e atributos listados são os únicos presentes.

Vamos agora a classe InjecaoEletronica:

public class InjecaoEletronica {
private Fabricante fabricante;

public void setFabricante(Fabricante fabricante) {
this.fabricante = fabricante;
}

public Fabricante getFabricante() {
return fabricante;
}
}

 

E então a classe Fabricante:

public class Fabricante {
private ArrayList<Cambio> cambios;

public Fabricante( ArrayList<Cambio> cambios ){
this.cambios = cambios;
}

public void addCambios(Cambio cambio) {
this.cambios.add( cambio );
}

public Cambio[] getCambios() {
return cambios.toArray();
}
}

 

Note que no código das classes acima não há garantia da criação dos objetos das variáveis de instância, esses objetos são injetados via método setter ou pelo construtor. Essa injeção de dados é também um padrão, Injeção de Dependência.

Para trabalharmos com Objeto Nulo para InjecaoEletronica vamos utilizar a versão com Interface. Vamos começar definindo a Interface InjecaoEletronicaImpl:

public interface InjecaoEletronicaImpl {
public void setFabricante(Fabricante fabricante);

public Fabricante getFabricante();
}

 

Veja que ela deve ter a assinatura dos métodos públicos da classe concreta, InjecaoEletronica. Somente os métodos públicos.

Vamos agora a nova assinatura da classe InjecaoEletronica:

public class InjecaoEletronica implements InjecaoEletronicaImpl {
...
}

 

E então devemos criar a classe que vai originar os objetos nulos de InjecaoEletronica, a classe ObjNuloInjecaoEletronica:

public class ObjNuloInjecaoEletronica implements InjecaoEletronicaImpl {

public void setFabricante(Fabricante fabricante) {}

public Fabricante getFabricante() {
return new ObjNuloFabricante(null);
}
}

 

Note que o Objeto Nulo deve ser independente de entradas externas de dados, por isso a instanciação de ObjNuloFabricante no método getFabricante().

Para ObjNuloFabricante vamos utilizar a versão com herança e sobrescrever todos os métodos públicos, incluindo o construtor:

public class ObjNuloFabricante extends Fabricante {

public ObjNuloFabricante(ArrayList<Cambio> cambios ){
super(null);
}

public void addCambios(Cambio cambio) {}

public Cambio[] getCambios() {
return new Cambio[0];
}
}

 

Note que criamos um array com 0 posições no método getCambios() para não dependermos de dados de entrada, nesse caso, os que vem no construtor.

Com isso podemos atualizar os códigos de Carro e InjecaoEletronica. Começando pela classe Carro:

public class Carro {
private InjecaoEletronicaImpl injecaoEletronica;

public Carro(){
this.injecaoEletronica = new ObjNuloInjecaoEletronica();
}

public void setInjecaoEletronica(InjecaoEletronicaImpl injecaoEletronica) {
this.injecaoEletronica = injecaoEletronica;
}

public InjecaoEletronicaImpl getInjecaoEletronica() {
return injecaoEletronica;
}
}

 

No código de classe anterior adicionamos um construtor para inicializar a variável de instância injecaoEletronica, atualizamos o tipo da variável para InjecaoEletronicaImpl e também os tipos de entrada e retorno dos métodos setInjecaoEletronica() e getInjecaoEletronica().

Seguindo para a atualização da classe InjecaoEletronica temos:

public class InjecaoEletronica implements InjecaoEletronicaImpl {
private Fabricante fabricante;

public InjecaoEletronica(){
fabricante = new ObjNuloFabricante(null);
}

public void setFabricante(Fabricante fabricante) {
this.fabricante = fabricante;
}

public Fabricante getFabricante() {
return fabricante;
}
}

 

Como a classe ObjNuloFabricante trabalha com herança, em InjecaoEletronica apenas precisamos adicionar o construtor para inicializar a variável de instância.

A classe Fabricante permanece a mesma, pois ela é nosso último nível, assumindo que quando houver uma instância direta de Fabricante, o código cliente vai entrar com uma implementação de ArrayList<Cambio> no construtor.

Reflexão. Você provavelmente deve estar intrigado com o valor null no construtor. Por que não um construtor vazio?

Na verdade, no caso do construtor, não há problemas em trabalhar com um vazio, nesse trecho de código somente achei interessante seguir a linha dos métodos públicos, que devem ter na classe de Objeto Nulo a exata mesma assinatura.

Isso é necessário nos métodos públicos para que os trechos de código que esperam objetos reais com dados reais não tenham de ser alterados.

Os métodos públicos, como comportamento padrão, podem ter retornos vazios (0, "", false, ...) ou ter lógicas padrões que seriam nada mais nada menos que a representação de um trecho else de um condicional como o abaixo, na variação do método trocaCambio() em um código que utiliza verificação de nulos onde um Objeto Nulo seria uma outra alternativa:

...
public boolean trocaCambio(){
if( carro.getInjecaoEletronica() != null ){
/* TODO */
}
else{ /* SERÁ O RETORNO DO OBJ NULO NA CHAMADA A trocaCambio() */
carro.setCarburador( new Carburador() );
}
return false;
}
...

 

Assumindo uma classe Objeto Nulo para a classe que tem o método trocaCambio(), teríamos esse método com o seguinte conteúdo ao invés de vazio:

...
public boolean trocaCambio(){
carro.setCarburador( new Carburador() );
return false;
}
...

 

Agora podemos prosseguir para a última versão de Objeto Nulo.

Em algumas linguagens temos o código nativo, ou seja, a linguagem oferece suporte a implementação desse padrão, dando assim ainda mais ênfase a importância do padrão. Desde o Java 8 temos a entidade Optional.

Segue um exemplo de classe sem essa entidade Optional sendo utilizada, a classe Carro sem suporte a Objeto Nulo:

...
private Carro carro;
...

 

No código anterior somente temos a declaração da variável de instância carro, nada garante que no código teremos a ela a atribuição de um objeto do tipo Carro. Agora com a entidade Optional sendo utilizada:

...
private Carro carro;
Optional<Carro> carroOptional = Optional.ofNullable(carro);
...

 

No código anterior, devido ao valor inicial de carro ser null, temos um objeto Optional<Carro> vazio trabalhando como um Objeto Nulo. Caso carro fosse um objeto real carroOptional teria esse valor de objeto real, ou seja, os mesmos estados (atributos com os mesmos valores).

Linguagens como Scala, Haskel e C# também oferecem sintaxe para trabalho com Objetos Nulos.

O conteúdo sobre a entidade Optional de Java é um pouco mais extenso, porém simples e fácil de entender. E pode acreditar, no site da Oracle tem um artigo completo e em português falando somente desse camarada: Você está cansado das exceções de ponteiro nulo? Avalie a possibilidade de usar Optional do Java SE 8.

É importante notar que o padrão Objeto Nulo não veio para substituir por completo o valor null, na verdade o objetivo real dele é remover o trabalho com o valor null quando um Objeto Nulo já é o suficiente. Isso porque há casos onde realmente queremos um valor null como resposta para então projetar a saída correta para o usuário.

Um exemplo clássico de quando não devemos utilizar um Objeto Nulo é quando trabalhamos com entidades onde a instância com valores reais é necessária para a continuação do processamento do algoritmo.

Como exemplo temos o objeto da classe RecyclerView que pode ser obtido pelo método findViewById() em uma Activity do Java Android:

...
RecyclerView rvUsers = (RecyclerView) findViewById(R.id.rv_users);
rvUsers.setHasFixedSize( true ); /* CRITICO */
...

 

A linha rvUsers.setHasFixedSize( true ); coloquei somente para informar que o próprio IDE AndroidStudio marca essa linha como risco de problema, pois o objeto do tipo RecyclerView pode ainda não ter sido criado no layout da APP e consequentemente deve haver uma verificação de valor null antes de tentar invocar métodos na variável de instância rvUsers:

...
RecyclerView rvUsers = (RecyclerView) findViewById(R.id.rv_users);
if( rvUsers != null ){
rvUsers.setHasFixedSize( true );
...
}
...

 

Voltando aos códigos de Carro. Lembra daquele início com várias verificações? O mais inflado. Lembra dele? Recapitulando no código a seguir:

...
public boolean trocaCambio(){
if( carro.getInjecaoEletronica() != null
&& carro.getInjecaoEletronica().getFabricante() != null
&& carro.getInjecaoEletronica().getFabricante().getCambios() != null ){

Cambio[] cambios = carro.getInjecaoEletronica()
.getFabricante()
.getCambios();
/* TODO */
}
return false;
}
...

 

Assumindo que os objetos do domínio do problema poderiam ser criados remotamente e que o trabalho com objetos nulos nos pouparia verificações desnecessárias de valor null.

Tendo em mente que o usuário teria uma velha e conhecida tela de Loading... sendo apresentada a ele enquanto os objetos com valores reais não são criados. Aplicando esse padrão nas classes necessárias, o método acima ficaria:

...
public boolean trocaCambio(){
Cambio[] cambios = carro.getInjecaoEletronica()
.getFabricante()
.getCambios();
/* TODO */
}
...

 

Outro padrão de projeto

Lembra da tela de Loading... que comentei anteriormente? Que é muito comum em jogos. Essa tela de Loading... na verdade é parte de um padrão de projeto chamado Proxy Virtual. Esse Proxy Virtual faz parte de uma família de padrões Proxy, são eles: Virtual, Convencional e Dinâmico (esse último é também conhecido como Proxy de Segurança).

Pontos negativos

  • Pode complicar e tirar performance do projeto caso a implementação com verificação de null, if( objeto != null ), seja menos custosa em número de linhas de código, por exemplo;
  • Caso o padrão não seja aplicado da maneira a trabalhar com Interface (estrutura de linguagem) e outros programadores do projeto não estejam ciente da aplicação dele no algoritmo, esses outros developers podem acabar colocando verificações para valores null quando esses nunca ocorreram, inflando o código ainda mais.

Pontos positivos

  • Como com todos os padrões de projeto, o Objeto Nulo aplica uma linguagem universal ao projeto, facilitando a leitura e entendimento dele por parte de outros programadores;
  • Remove a necessidade de ter lógica especial para verificarão de valor null, consequentemente não inflando o projeto de software.

Conclusão

O padrão Objeto Nulo é comumente utilizado com os padrões de projeto Strategy e Factory Method. Em algumas ocasiões é utilizado também com o padrão Observer.

Um problema que pode surgir caso a sua implementação do objeto nulo esteja utilizando herança é que se um novo método for adicionado a superclasse, os programadores do projeto terão de lembrar de atualizar a classe do Objeto Nulo, caso contrário eles poderão criar um script que pode trabalhar com uma instância do Objeto Nulo da superclasse atualizada, podendo ocasionar em NullPointerException quando chamando o novo método nessa instância de Objeto Nulo.

Uma dica para a utilização do padrão Objeto Nulo é verificar se seu código dentro do condicional if( objeto != null ) pode ou não trabalhar com valores vazios ou valores padrões. Caso sim, Objeto Nulo é uma provável melhor escolha.

Fontes

Rafatoração para Padrões de Projeto

Null Object Pattern por Marco Baccaro

Você está cansado das exceções de ponteiro nulo? Avalie a possibilidade de usar Optional do Java SE 8

Vlw.

Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação.
Email inválido

Relacionado

Padrão de Projeto: Simple FactoryPadrão de Projeto: Simple FactoryAndroid
Padrão de Projeto: Factory MethodPadrão de Projeto: Factory MethodAndroid
Padrão de Projeto: Abstract FactoryPadrão de Projeto: Abstract FactoryAndroid
Padrão de Projeto: AdapterPadrão de Projeto: AdapterAndroid

Compartilhar

Comentários Facebook

Comentários Blog

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...