O Nil (ou Null) Object Pattern é um padrão em programação Orientada a Objetos que descreve o uso de objetos nulos e o seu comportamento. Na realidade, o comportamento de objetos nulos é neutro e o Nil Object Pattern nos permite controlar como nossa aplicação responde a eles.
Esse padrão pode ser implementado em diversos contextos, mas aqui vamos descrevê-lo dentro de uma aplicação Ruby on Rails fictícia que simula o sistema interno de uma locadora de veículos.
Nessa aplicação utilizamos a gem Devise para gerenciar o sistema de login dos usuários. Existem usuários gerentes, com funções restritas, e administradores, que têm acesso a todas as funcionalidades do sistema. Além disso, visitantes não cadastrados podem visualizar os veículos disponíveis para locação.
Como as funcionalidades disponíveis são diferentes para cada tipo de usuário, é necessário realizar uma série de verificações dentro dos Controllers da aplicação para que sejam autorizadas apenas funcionalidades de acordo com quem está acessando o sistema. Por essa razão, métodos como current_user
são utilizados frequentemente e, nesse ponto, nos deparamos com um problema: se não houver usuário logado, o método não sabe como responder.
Uma das vantagens de utilizar o Devise é que alguns métodos ficam disponíveis – como o current_user.admin?
, que verifica se o usuário logado é admin, por exemplo. No entanto, dentro do cenário descrito aqui em que temos um visitante navegando no site, ou seja, não há um usuário logado, esse método não funciona corretamente.
Uma maneira elegante de resolver esse problema é utilizar o Nil Object Pattern. Na nossa aplicação, quando um usuário não logado utiliza o sistema, métodos que verificam a presença de um usuário logado retornam nulo (nil
em Ruby), mas, nesse caso, nulo não é o mesmo que nenhum usuário.
Primeiramente, criamos uma classe NilUser
:
class NilUser
end
Agora, sobrescrevemos o método current_user
dentro do Application Controller
para que ele ainda responda da maneira tradicional, mas, na ausência de um usuário, ele deve instanciar um objeto NilUser
:
class ApplicationController < ActionController
def current_user
super || NilUser.new
end
end
Desta forma, toda vez que algum método falha com current_user
, ele pode ser sobrescrito dentro da classe NilUser
. Podemos, por exemplo, já implementar o método admin?
para que ele retorne falso, afinal, um usuário não logado não é um administrador:
class NilUser
def admin?
false
end
end
Podemos ir além e implementar um método email
, que retorna ""
, e guest?
, que retorna verdadeiro:
class NilUser
def admin?
false
end
def email
""
end
def guest?
true
end
end
Assim, temos controle sobre as respostas esperadas para todos cenários em que um usuário é nulo. Por essa razão, deixa de ser necessário verificar a todo momento se as variáveis são nulas. Além disso, todo o código que define esse comportamento fica dentro de uma única classe NillUser
, em vez de ficar espalhado dentro dos Controllers e Views da aplicação.