Trilhas
/
Crystal
Crystal
/
Exercícios
/
Trava de senha
Trava de senha

Trava de senha

Exercício de aprendizagem

Introdução

O Crystal permite que uma variável tenha vários tipos. Isso é chamado de tipo união. No Crystal, é comum que o compilador infira um tipo união.

Note

Um tipo união ainda é um único tipo em tempo de execução, mesmo que seja formado por vários tipos. Isso significa que, se um tipo união for formado por String e Int32, ele não será os dois ao mesmo tempo. Em vez disso, ele será ou uma String ou um Int32.

Um tipo união é declarado separando os tipos com uma barra vertical (|). Eles costumam ser colocados entre parênteses, mas isso não é obrigatório. O tipo união mais comum é (T | Nil), em que T é um tipo, e pode aparecer em métodos que podem retornar Nil. Isso também é conhecido como uma variável ser nilable.

foo : String = "Hello"
foo = nil # Error: type must be String, not (String | Nil)

foo : (String | Nil) = "Hello"
foo = nil

Ele não se limita a apenas dois tipos; pode ter quantos você quiser.

foo : (String | Int32 | Nil | Float64) = "Hello"
foo = 1
foo = nil
foo = 1.0

typeof vs Object#class

Existem duas formas de obter o tipo de uma variável. Usando typeof ou usando Object#class. A diferença é que typeof retorna o tipo da variável em tempo de compilação, enquanto Object#class retorna o tipo em tempo de execução. Isso significa que, se você quiser ver, por exemplo, se uma variável é um tipo união, o Object#class não vai conseguir te dizer isso, porque ele só retorna o tipo em tempo de execução, que é um único tipo.

foo = 0 == 0 ? "a" : 1
typeof(foo) # => (String | Int32)
foo.class # => String

Operações com tipos união

Como um tipo união é um único tipo em tempo de execução, todas as operações padrão funcionam nele. Mas, ao compilar o código, o compilador precisa saber qual é o tipo. Por isso, o código precisa ser estruturado de forma que só possa ser um dos tipos quando você quiser usar as operações específicas de um tipo.

foo : (String | Int32) = "Hello"
foo.downcase # Error: undefined method 'downcase' for (String | Int32)

O Crystal tem um método específico para tipos união: o método is_a?, que recebe um tipo como argumento e retorna um Boolean. O método nil? é um atalho para is_a?(Nil). Colocar o método is_a? em uma expressão de controle diz ao compilador qual é o tipo e, com isso, garante que ele é aquele tipo. E, para um bloco else, fica garantido que ele não é aquele tipo.

foo : (String | Int32) = "Hello"
if foo.is_a?(String)
  typeof(foo)  # => String
  foo.downcase # => "hello"
end

Esse is_a? não se limita a ter um único tipo como argumento; ele também pode receber um tipo união. Ele também pode ser combinado com && para vários tipos.

Note

O método is_a?, quando usado em conjunto com uma expressão de controle, não pode ser uma variável de instância ou uma variável de classe. Em vez disso, elas precisam primeiro ser atribuídas a uma variável local.

as

Uma forma de transformar um tipo união em um único tipo é fazer com que um bloco só possa ser executado se o tipo for um tipo específico. Outra abordagem é usar o método as. Ele transforma um tipo união em um único tipo fazendo uma verificação em tempo de execução. Uma exceção será lançada se o tipo não for o esperado.

foo : String | Int32 = "Hello"
foo.as(String).downcase # => "hello"

foo.as(Int32) # Error: can't cast String to Int32
Caution

Essa abordagem serve apenas para quando você tem certeza de que o tipo é o esperado, ou para quando você quer lançar uma exceção quando não for.

Usar essa abordagem com uma configuração inadequada pode levar a um comportamento inesperado.

as?

as? funciona de forma muito parecida com as, mas retorna nil em vez de lançar uma exceção se o tipo não for o esperado. Isso significa que ele retorna um tipo união do tipo esperado e Nil.

foo : (String | Int32) = "Hello"
foo.as?(String).downcase # => "hello"

foo.as?(Int32) # => nil

Forma abreviada de nilable

Nilable significa que uma variável pode ser um tipo ou Nil. Isso pode ser escrito como (T | Nil). Mas, como os tipos nilable são relativamente comuns, existe uma forma abreviada para isso: T?.

# This:
foo : (String | Nil) = "Hello"
foo = nil

# Is the same as:
foo : String? = "Hello"
foo = nil

Instruções

A empresa em que você trabalha está prestes a lançar seu smartphone novinho em folha, o Smarty 5. Ele traz um sistema de câmera totalmente novo, um design novo e um processador revolucionário que roda um novo sistema operacional chamado smartyOS.

O novo processador tem potência para lidar com senhas mais seguras do que nunca, e a empresa decidiu usar isso a seu favor. Eles pediram que você implemente um sistema de bloqueio por senha para o telefone, que permita ao usuário definir uma senha e depois verificar se uma senha informada está correta.

Caution

O sistema de senhas usado neste exercício não é seguro e serve apenas para fins educacionais. Ele NÃO deve ser usado em aplicações do mundo real.

1. Defina uma senha

O telefone permite que os usuários usem vários tipos diferentes de senha, de uma senha simples de dígitos a uma senha alfanumérica mais segura, e até uma senha que armazena a impressão digital.

Esses diferentes tipos de senha usam tipos diferentes: a senha de dígitos usa um Int32, a senha alfanumérica usa uma String, e a senha de impressão digital usa um Float64.

Implemente a classe PasswordLock com um inicializador que recebe qualquer um dos três tipos de senha como argumento e o armazena em uma variável de instância chamada @password.

password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>

2. Criptografe a senha

A empresa decidiu criptografar a senha para que ela não seja armazenada em texto simples e pediu que você implemente um método para criptografá-la.

Cada tipo de senha tem uma forma única de criptografia:

  • Senha Int32: a senha é dividida por dois e arredondada para o número inteiro mais próximo.
  • Senha String: a senha é invertida.
  • Senha Float64: a senha é multiplicada por quatro.

Implemente um método de instância chamado encrypt que não recebe argumentos e modifica a variável de instância @password para que ela fique criptografada.

password_lock = PasswordLock.new(1234)
# => #<PasswordLock:0x7f8e1b8c0b80 @password=1234>

password_lock.encrypt
# => #<PasswordLock:0x7f8e1b8c0b80 @password=617>

3. Verifique se uma senha está correta

A empresa também pediu que você implemente um método que verifica se uma senha informada está correta.

Eles querem que o método retorne nil se a senha estiver incorreta e "Unlocked" se a senha estiver correta.

Implemente um método de instância chamado unlock? que recebe como argumento a senha a ser verificada. O método deve retornar nil se a senha estiver incorreta e "Unlocked" se a senha estiver correta.

password_lock = PasswordLock.new(1234)
password_lock.encrypt
password_lock.unlock?(1234)
# => "Unlocked"
Editar via GitHub O link abre em uma nova janela ou aba
Crystal Exercism

Tudo pronto para começar Trava de senha?

Crie sua conta no Exercism para aprender e dominar Crystal com 26 conceitos142 exercícios e mentoria humana de verdade, tudo de graça.