Percursos
/
Crystal
Crystal
/
Exercícios
/
Fechadura com Palavra-passe
Fechadura com Palavra-passe

Fechadura com Palavra-passe

Exercício de aprendizagem

Introdução

O Crystal permite que uma variável seja de vários tipos. Isto chama-se tipo união. No Crystal, é comum o compilador inferir um tipo união.

Note

Um tipo união continua a ser um único tipo em tempo de execução, mesmo que seja composto por vários tipos. Isto significa que, se um tipo união for construído com String e Int32, não será ambos ao mesmo tempo. Será antes um String ou um Int32.

Um tipo união declara-se separando os tipos com uma barra vertical (|). Aparecem frequentemente entre parênteses, mas isso não é obrigatório. O tipo união mais comum é (T | Nil), em que T é um tipo, e pode surgir em métodos que podem devolver Nil. Isto 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

Não está limitado a dois tipos: pode ter tantos quantos quiseres.

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

typeof vs Object#class

Há duas formas de obter o tipo de uma variável. Ou com typeof, ou com Object#class. A diferença é que typeof devolve o tipo da variável em tempo de compilação, enquanto Object#class devolve o tipo em tempo de execução. Isto significa que, se quiseres ver, por exemplo, se uma variável é um tipo união, Object#class não to poderá dizer, porque só devolve 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 sobre tipos união

Como um tipo união é um único tipo em tempo de execução, todas as operações normais funcionam com ele. Mas, ao compilar o código, o compilador precisa de saber de que tipo se trata. Por isso, o código tem de estar organizado de forma a que só possa ser um dos tipos quando queres usar as operações específicas desse tipo.

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

O Crystal tem de facto um método específico para tipos união: o método is_a?, que recebe um tipo como argumento e devolve um Boolean. O método nil? é um atalho para is_a?(Nil). Colocar o método is_a? numa expressão de controlo indica ao compilador de que tipo se trata e, assim, garante que é esse o tipo. E, num ramo else, fica garantido que não é esse o tipo.

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

Este is_a? não está limitado a receber um único tipo como argumento: também pode receber um tipo união. 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 controlo, não pode ser uma variável de instância nem uma variável de classe. Em vez disso, estas têm de ser atribuídas primeiro a uma variável local.

as

Uma forma de transformar um tipo união num único tipo é fazer com que um ramo só possa ser executado se o tipo for um tipo específico. Outra abordagem é usar o método as. Este transforma um tipo união num único tipo através de uma verificação em tempo de execução. É lançada uma exceção 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

Esta abordagem destina-se apenas a quando tens a certeza de que o tipo é o esperado ou a quando queres lançar uma exceção se não for.

Usar esta abordagem com uma configuração inadequada pode levar a comportamentos inesperados.

as?

as? funciona de forma muito semelhante a as, mas devolve nil em vez de lançar uma exceção se o tipo não for o esperado. Isto significa que devolve um tipo união do tipo esperado com Nil.

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

foo.as?(Int32) # => nil

Abreviatura de nilable

Nilable significa que uma variável pode ser um tipo ou Nil. Pode escrever-se como (T | Nil). Mas, como os tipos nilable são relativamente comuns, existe uma abreviatura: T?.

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

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

Instruções

A empresa para a qual trabalhas está prestes a lançar o seu novo smartphone, chamado Smarty 5. Tem um sistema de câmara novinho em folha, um novo design e um processador revolucionário que corre um novo sistema operativo chamado smartyOS.

O novo processador tem potência para lidar com palavras-passe mais seguras do que nunca, e a empresa decidiu tirar partido disso. Pediram-te para implementares um sistema de bloqueio por palavra-passe para o telefone, que permita ao utilizador definir uma palavra-passe e depois verificar se uma palavra-passe fornecida está correta.

Caution

O sistema de palavras-passe praticado neste exercício não é seguro e serve apenas para fins educativos. NÃO deve ser usado em nenhuma aplicação do mundo real.

1. Define uma palavra-passe

O telefone permite aos utilizadores usar vários tipos diferentes de palavras-passe, desde uma simples palavra-passe de algarismos até uma palavra-passe alfanumérica mais segura e até uma palavra-passe que guarda a impressão digital.

Estes diferentes tipos de palavras-passe usam tipos diferentes: a palavra-passe de algarismos usa um Int32, a palavra-passe alfanumérica usa uma String e a palavra-passe de impressão digital usa um Float64.

Implementa a classe PasswordLock com um inicializador que recebe qualquer um dos três tipos de palavras-passe como argumento e o guarda numa variável de instância chamada @password.

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

2. Encripta a palavra-passe

A empresa decidiu encriptar a palavra-passe para que esta não fique guardada em texto simples e pediu-te para implementares um método que a encripte.

Cada tipo de palavra-passe tem a sua forma única de ser encriptado:

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

Implementa um método de instância chamado encrypt que não recebe argumentos e que modifica a variável de instância @password, de forma a que fique encriptada.

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

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

3. Verifica se uma palavra-passe está correta

A empresa também te pediu para implementares um método que verifique se uma palavra-passe fornecida está correta.

Querem que o método devolva nil se a palavra-passe estiver incorreta e "Unlocked" se a palavra-passe estiver correta.

Implementa um método de instância chamado unlock? que recebe como argumento a palavra-passe a verificar. O método deve devolver nil se a palavra-passe estiver incorreta e "Unlocked" se a palavra-passe estiver correta.

password_lock = PasswordLock.new(1234)
password_lock.encrypt
password_lock.unlock?(1234)
# => "Unlocked"
Editar via GitHub A ligação abre numa nova janela ou separador
Crystal Exercism

Estás pronto para começar Fechadura com Palavra-passe?

Inscreve-te no Exercism para aprenderes e dominares Crystal com 26 conceitos142 exercícios, e mentoria humana real, tudo grátis.