Διαδρομές
/
Crystal
Crystal
/
Ασκήσεις
/
Κλείδωμα με κωδικό
Κλείδωμα με κωδικό

Κλείδωμα με κωδικό

Άσκηση εκμάθησης

Εισαγωγή

Η Crystal επιτρέπει σε μια μεταβλητή να αποτελείται από πολλούς τύπους. Αυτό ονομάζεται τύπος ένωσης. Στη Crystal, είναι συνηθισμένο ένας τύπος ένωσης να συμπεραίνεται από τον μεταγλωττιστή.

Note

Ένας τύπος ένωσης παραμένει ένας μόνο τύπος κατά τον χρόνο εκτέλεσης, ακόμα κι αν αποτελείται από πολλούς τύπους. Αυτό σημαίνει ότι αν ένας τύπος ένωσης αποτελείται από String και Int32, δεν θα είναι και τα δύο ταυτόχρονα. Αντίθετα, θα είναι είτε String είτε Int32.

Ένας τύπος ένωσης δηλώνεται χωρίζοντας τους τύπους με μια κάθετη γραμμή (|). Συχνά τοποθετούνται μέσα σε παρενθέσεις, αλλά αυτό δεν είναι υποχρεωτικό. Ο πιο συνηθισμένος τύπος ένωσης είναι ο (T | Nil), όπου το T είναι ένας τύπος, και μπορεί να εμφανιστεί σε μεθόδους που μπορούν να επιστρέψουν Nil. Αυτό είναι επίσης γνωστό ως nilable μεταβλητή.

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

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

Δεν περιορίζεται σε δύο μόνο τύπους, αλλά μπορεί να έχει όσους θέλεις.

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

typeof έναντι Object#class

Υπάρχουν δύο τρόποι για να βρεις τον τύπο μιας μεταβλητής. Είτε χρησιμοποιώντας το typeof είτε χρησιμοποιώντας το Object#class. Η διαφορά είναι ότι το typeof επιστρέφει τον τύπο μιας μεταβλητής κατά τον χρόνο μεταγλώττισης, ενώ το Object#class επιστρέφει τον τύπο κατά τον χρόνο εκτέλεσης. Αυτό σημαίνει ότι αν θέλεις να δεις, για παράδειγμα, αν μια μεταβλητή είναι τύπος ένωσης, το Object#class δεν θα μπορέσει να σου το πει, καθώς θα επιστρέψει μόνο τον τύπο κατά τον χρόνο εκτέλεσης, που είναι ένας μόνο τύπος.

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

Πράξεις σε τύπους ένωσης

Εφόσον ένας τύπος ένωσης είναι ένας μόνο τύπος κατά τον χρόνο εκτέλεσης, όλες οι τυπικές πράξεις λειτουργούν πάνω του. Όμως, όταν μεταγλωττίζεται ο κώδικας, ο μεταγλωττιστής θα χρειαστεί να ξέρει ποιος τύπος είναι. Γι' αυτό ο κώδικας πρέπει να είναι γραμμένος με τέτοιον τρόπο ώστε να μπορεί να είναι μόνο ένας από τους τύπους, όταν θέλεις να χρησιμοποιήσεις πράξεις που είναι ειδικές για τον τύπο.

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

Η Crystal έχει όντως μια ειδική μέθοδο για τους τύπους ένωσης: τη μέθοδο is_a?, η οποία παίρνει έναν τύπο ως όρισμα και επιστρέφει Boolean (λογική τιμή). Η μέθοδος nil? είναι μια συντόμευση για το is_a?(Nil). Αν τοποθετήσεις τη μέθοδο is_a? σε μια έκφραση ελέγχου, θα πεις στον μεταγλωττιστή ποιος τύπος είναι και έτσι θα εγγυηθείς ότι είναι αυτός ο τύπος. Και στη διακλάδωση else θα είναι εγγυημένο ότι δεν είναι αυτός ο τύπος.

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

Αυτό το is_a? δεν περιορίζεται στο να έχει έναν μόνο τύπο ως όρισμα, αλλά μπορεί να δεχτεί και τύπο ένωσης. Μπορεί επίσης να συνδυαστεί με && για πολλούς τύπους.

Note

Όταν χρησιμοποιείς τη μέθοδο is_a? σε συνδυασμό με μια έκφραση ελέγχου, δεν μπορεί να είναι μεταβλητή στιγμιοτύπου ή μεταβλητή κλάσης. Αντ' αυτού, πρέπει να ανατεθούν πρώτα σε μια τοπική μεταβλητή.

as

Ένας τρόπος για να μετατρέψεις έναν τύπο ένωσης σε έναν μόνο τύπο είναι να φροντίσεις ώστε μια διακλάδωση να μπορεί να εκτελεστεί μόνο αν ο τύπος είναι κάποιος συγκεκριμένος τύπος. Μια άλλη προσέγγιση είναι να χρησιμοποιήσεις τη μέθοδο as. Αυτή μετατρέπει έναν τύπο ένωσης σε έναν μόνο τύπο κάνοντας έναν έλεγχο κατά τον χρόνο εκτέλεσης. Αν ο τύπος δεν είναι ο αναμενόμενος, θα πεταχτεί μια εξαίρεση.

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

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

Αυτή η προσέγγιση προορίζεται μόνο για όταν είσαι σίγουρος ότι ο τύπος είναι ο αναμενόμενος ή για όταν θέλεις να πετάξεις μια εξαίρεση όταν δεν είναι.

Η χρήση αυτής της προσέγγισης με ακατάλληλη ρύθμιση μπορεί να οδηγήσει σε μη αναμενόμενη συμπεριφορά.

as?

Το as? λειτουργεί με πολύ παρόμοιο τρόπο με το as, αλλά θα επιστρέψει nil αντί να πετάξει εξαίρεση, αν ο τύπος δεν είναι ο αναμενόμενος. Αυτό σημαίνει ότι θα επιστρέψει έναν τύπο ένωσης που αποτελείται από τον αναμενόμενο τύπο και το Nil.

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

foo.as?(Int32) # => nil

Συντομογραφία για nilable

Nilable σημαίνει ότι μια μεταβλητή μπορεί να είναι είτε ένας τύπος είτε Nil. Αυτό μπορεί να γραφτεί ως (T | Nil). Αλλά επειδή οι nilable τύποι είναι σχετικά συνηθισμένοι, υπάρχει μια συντομογραφία γι' αυτό: T?.

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

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

Οδηγίες

Η εταιρεία στην οποία εργάζεσαι είναι έτοιμη να κυκλοφορήσει το ολοκαίνουργιο smartphone της, με το όνομα Smarty 5. Διαθέτει ένα ολοκαίνουργιο σύστημα κάμερας, έναν νέο σχεδιασμό και έναν επαναστατικό νέο επεξεργαστή που τρέχει σε ένα νέο λειτουργικό σύστημα, το smartyOS.

Ο νέος επεξεργαστής έχει τη δύναμη να χειρίζεται πιο ασφαλείς κωδικούς πρόσβασης από ποτέ, και η εταιρεία αποφάσισε να το εκμεταλλευτεί αυτό προς όφελός της. Σου ζήτησε να υλοποιήσεις ένα σύστημα κλειδώματος με κωδικό πρόσβασης για το τηλέφωνο, που θα επιτρέπει στον χρήστη να ορίζει έναν κωδικό πρόσβασης και μετά να ελέγχει αν ένας δεδομένος κωδικός είναι σωστός.

Caution

Το σύστημα κωδικών πρόσβασης που εξασκείς σε αυτή την άσκηση δεν είναι ασφαλές και χρησιμοποιείται μόνο για εκπαιδευτικούς σκοπούς. ΔΕΝ θα πρέπει να χρησιμοποιηθεί σε καμία πραγματική εφαρμογή.

1. Όρισε έναν κωδικό πρόσβασης

Το τηλέφωνο επιτρέπει στους χρήστες να χρησιμοποιούν πολλούς διαφορετικούς τύπους κωδικών πρόσβασης, από έναν απλό κωδικό με ψηφία μέχρι έναν πιο ασφαλή αλφαριθμητικό κωδικό και ακόμη και έναν κωδικό που αποθηκεύει το αποτύπωμά τους.

Αυτοί οι διαφορετικοί τύποι κωδικών χρησιμοποιούν διαφορετικούς τύπους: ο κωδικός με ψηφία χρησιμοποιεί ένα Int32, ο αλφαριθμητικός κωδικός χρησιμοποιεί ένα String και ο κωδικός αποτυπώματος χρησιμοποιεί ένα Float64.

Υλοποίησε την κλάση PasswordLock με έναν αρχικοποιητή που δέχεται οποιονδήποτε από τους τρεις τύπους κωδικών ως όρισμα και τον αποθηκεύει σε μια μεταβλητή στιγμιοτύπου με το όνομα @password.

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

2. Κρυπτογράφησε τον κωδικό πρόσβασης

Η εταιρεία αποφάσισε να κρυπτογραφήσει τον κωδικό πρόσβασης, ώστε να μην αποθηκεύεται σε απλό κείμενο, και σου ζήτησε να υλοποιήσεις μια μέθοδο για την κρυπτογράφησή του.

Κάθε τύπος κωδικού έχει τον δικό του μοναδικό τρόπο κρυπτογράφησης:

  • Κωδικός Int32: Ο κωδικός διαιρείται με το δύο και στρογγυλοποιείται στον πλησιέστερο ακέραιο.
  • Κωδικός String: Ο κωδικός αντιστρέφεται.
  • Κωδικός Float64: Ο κωδικός πολλαπλασιάζεται επί τέσσερα.

Υλοποίησε μια μέθοδο στιγμιοτύπου με το όνομα encrypt που δεν δέχεται ορίσματα και τροποποιεί τη μεταβλητή στιγμιοτύπου @password, ώστε να είναι κρυπτογραφημένη.

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

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

3. Έλεγξε αν ένας κωδικός πρόσβασης είναι σωστός

Η εταιρεία σου ζήτησε επίσης να υλοποιήσεις μια μέθοδο που ελέγχει αν ένας δεδομένος κωδικός πρόσβασης είναι σωστός.

Θέλουν η μέθοδος να επιστρέφει nil αν ο κωδικός είναι λανθασμένος και "Unlocked" αν ο κωδικός είναι σωστός.

Υλοποίησε μια μέθοδο στιγμιοτύπου με το όνομα unlock? που δέχεται ως όρισμα τον κωδικό προς έλεγχο. Η μέθοδος θα πρέπει να επιστρέφει nil αν ο κωδικός είναι λανθασμένος και "Unlocked" αν ο κωδικός είναι σωστός.

password_lock = PasswordLock.new(1234)
password_lock.encrypt
password_lock.unlock?(1234)
# => "Unlocked"
Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
Crystal Exercism

Έτοιμος να ξεκινήσεις την άσκηση Κλείδωμα με κωδικό;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις Crystal με 26 έννοιες142 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.