Design Patterns: Padrão Sentinel Object

Daniel Rigoni
luizalabs
Published in
5 min readOct 30, 2020

Olá pessoal, vamos falar um pouquinho hoje sobre design patterns?

Quando desenvolvemos um software, é natural encontrarmos desafios. Conforme adquirimos experiência, percebemos que determinados problemas são recorrentes, mesmo em projetos diferentes. Automaticamente, nos lembramos de como solucionamos um desafio parecido anteriormente. É quase como se houvesse um padrão para solucionar certos tipos de problemas que encontramos. Esses padrões são conhecidos como Design Patterns ou Padrões de Códigos.

O que são Design Patterns?

Os padrões de design ou padrões de projetos são soluções para os problemas de design de software que você encontra repetidamente no desenvolvimento de aplicativos no mundo real.

Os 23 padrões de Gang of Four (GoF) são geralmente considerados a base para todos os outros padrões. Eles são classificados em três categorias: Criação, Estrutural e Comportamental.

Os 23 padrões do GOF organizados por suas três categorias

Além do Design Pattern do GOF, existem vários outros patterns como, por exemplo, Connected Buildings, Car Connection, Positive Outdoor Space e vários outros, esses patters somam cerca de 125 padrões.

O Design Patterns do GOF possui um formato, são eles:

  • Nome: Uma identificação para o patterns;
  • Objetivo / Intenção: Também conhecido como (Also Known As);
  • Motivação: Um cenário mostrando o problema e a necessidade;
  • Aplicabilidade: Como identificar as situações nas quais os padrões é aplicável;
  • Estrutura: Uma representação gráfica da estrutura das classes usando um diagrama de classes (UML);
  • Consequências: Vantagens e desvantagem;
  • Implementações: Quais detalhes devemos nos preocupar quando implementamos o padrão. Detalhes de cada linguagem;
  • Usos conhecidos;
  • Padrões Relacionados.

Essa foi apenas uma pequena introdução para que você saiba o que são design patterns. Entre esses, escolhi falar especificamente sobre o chamado The Sentinel Object Pattern. Esse é um design pattern específico do Python. Mas, para isso, é importante entendermos primeiro o conceito do Sentinel Value.

Sentinel Value

Na programação, um valor de sentinela é um valor no contexto de um algoritmo que utiliza a sua presença como uma condição de terminação, normalmente em algum loop ou algoritmo recursivo.

Por exemplo:

sum = 0
data = int(input("How long? (enter 0 to end) "))
while data != 0:
sum = sum + data
data = int(input("How long? (enter 0 to end) "))
print("Sum:",sum)

Neste exemplo, o valor de entrada 0 seria o valor de sentinela para este loop.

Um outro exemplo é o método str.find() do Python que retorna o índice da primeira ocorrência da substring, se encontrada. Quando a substring não for encontrada, retorna o valor sentinela -1.

i = a.find(b)
if i == -1:
return

Este é um exemplo clássico de valor sentinela. O valor -1é simplesmente um número inteiro, assim como os outros valores de retorno possíveis da função, mas com um significado especial que foi acordado com antecedência.

Ai que mora o problema.

Pode ser que o programador esqueça de verificar esse retorno e tente usá-lo como um índice na string! O resultado provavelmente não será o que o programador esperava.

Se str.find()tivesse sido inventado hoje, provavelmente ele teria usado o padrão Sentinel Object que descreveremos abaixo simplesmente retornando Nonepara “não encontrado”. Isso não deixaria nenhuma possibilidade de o valor de retorno ser usado acidentalmente como um índice.

Os valores de sentinela são particularmente comuns hoje na linguagem Go, cujo design incentiva um estilo de programação que sempre retorna strings em vez de meras referências a eles — forçando o programador a escolher alguma string específica, como a string vazia ou uma sentinela única especial, para indicar que nenhum dado foi coletado ou está presente.

Sentinel Object

Em Python, geralmente existem valores padrões. O valor padrão mais conhecido é o NoneNoneé um objeto de significado vago que quase grita “Eu sou um padrão”. Mas às vezes None é um valor válido e às vezes você deseja detectar o caso de “nenhum valor fornecido” e oNone dificilmente pode ser chamado de nenhum valor.

Aqui está um exemplo:

def getuser(username, default=None):
if not user_exists(username):
return default
...

Nesse caso, há sempre um padrão e, portanto, sempre que você chamar getuser() , deverá verificar se há um resultado None. Mas talvez você tenha um código no qual realmente gostaria de obter uma exceção se o usuário não for encontrado. Para conseguir isso, você pode usar uma sentinela. Uma sentinela é um objeto que não tem nenhum significado particular, exceto para sinalizar o fim, ou uma condição especial. Praticamente o significado seria mesmo que vimos anteriormente para o valor de sentinela.

Ficando dessa maneira:

_no_default = ()
def getuser(username, default=_no_default):
if not user_exists(username):
if default is _no_default:
raise LookupError("No user with the username %r" % username)
return default
...

Depois de entender o padrão, isso é fácil de ler. Mas quando você usa help() ou outra geração automática, fica um pouco confuso, porque o valor padrão aparece apenas como (). Você também pode usar object() ou [] ou qualquer outra coisa, mas a documentação gerada automaticamente ainda não terá uma aparência tão boa.

Exemplo utilizando o pydoc

Então, para darmos uma melhorada no código, poderiamos fazer dessa maneira:

class _NoDefault(object):
def __repr__(self):
return '(no default)'
NoDefault = _NoDefault()
del _NoDefault

def getuser(username, default=NoDefault):
...

E agora na documentação, ficaria mais claro o que o paramêtro default é e qual o sentido dele.

Exemplo utilizando o pydoc

Você pode então pensar Por que não existe um NoDefault que todos possam compartilhar?

Se você compartilhar esse sentinela, corremos o risco de passar acidentalmente esse valor, mesmo sem querer. O valor NoDefault ficará sobrecarregado de significado, assim como None. Por ter um objeto sentinela mais privado, você evita isso.

Espero que tenham gostado do post.

Abraços e até uma próxima! ;-)

--

--