Δομές ελέγχου ροής: switch statement

Στο προηγούμενο κεφάλαιο, μάθαμε για το if...else statement και πώς μπορούμε να εκτελέσουμε κώδικα βάσει μιας συνθήκης. Και ενώ το if...else statement καλύπτει όλες μας τις ανάγκες όσον αφορά την υπό συνθήκη εκτέλεση ενός block κώδικα, το switch statement μπορεί να μας βοηθήσει να επιτύχουμε παρόμοια αποτελέσματα, προσφέροντας μια πιο “καθαρή” και ευανάγνωστη λύση.

Σε αυτό το κεφάλαιο, λοιπόν, θα μιλήσουμε για το switch statement, τα βασικά χαρακτηριστικά και τη χρησιμότητά του, καθώς και τις διαφορές του με το if...else statement.

Το switch statement

Το switch statement είναι ένας τρόπος να ελέγχουμε τη ροή του προγράμματός μας, επιλέγοντας διαφορετικά “μονοπάτια” εκτέλεσης με βάση την τιμή μιας συγκεκριμένης έκφρασης (συνήθως μιας μεταβλητής).

Σκεφτείτε το σαν τον πίνακα με τα πλήκτρα σε έναν ανελκυστήρα. Όταν πιέσουμε κάποιο πλήκτρο από τον πίνακε–η τιμή του expression μας–ο ανελκυστήρας “διαβάζει” τον όροφο και μας μεταφέρει σε αυτό–εκτελείται το αντίστοιχο block κώδικα. Αν για κάποιο λόγο δεν πατήσουμε κάποιο πλήκτρο ή υπάρχει βλάβη, τότε ο ανεκλυστήρας θα κάνει αυτό που είναι προγραμματισμένος να κάνει σε αυτές τις περιπτώσεις (default behaviour): θα κλείσει τις πόρτες και θα παραμείνει ακίνητος. Στο παράδειγμα αυτό η τιμή που μας ενδιαφέρει είναι το σήμα που θα μεταφέρει το πλήκτρο στον πίνακα και η διεργασία που θα εκτελεστεί είναι η μετακίνηση του ανεκλυστήρα.

Συγκρίνοντας αυτή τη προσέγγιση με τον τρόπο που λειτουργεί το if statement, βλέπουμε ότι δεν έχουμε να κάνουμε μόνο με την ικανοποίηση κάποιας συνθήκης (πχ., αν θα πατήσω κάποιο πλήκτρο), αλλά και με την ίδια την τιμή. Αυτή είναι και η βασική διαφορά μεταξύ των δύο conditional statements.

Σύνταξη switch

Ένας ακόμα λόγος που χρησιμοποιούμε το switch statement είναι η βελτίωση της αναγνωσιμότητας του κώδικα. Όταν έχουμε πολλά if...else if statements που ελέγχουν την ίδια μεταβλητή για διαφορετικές τιμές, ο κώδικας μπορεί να γίνει μακροσκελής και δύσκολος στην ανάγνωση. To switch μας βοηθά να οργανώσουμε αυτά τα σενάρια με πιο δομημένο τρόπο.

Ένα switch statement συντάσσεται ως εξής:

switch (expression) {
  case τιμή_1:
    // Κώδικας αν expression === τιμή_1
    break;

  case τιμή_2:
    // Κώδικας αν expression === τιμή_2
    break;

  case τιμή_N:
    // Κώδικας για την Ν-οστή περίπτωση
    break;

  default:
    // Κώδικας αν καμία περίπτωση δεν ταιριάζει
    break;
}

Παράδειγμα

Για να κατανοήσουμε καλύτερα πώς συντάσσεται ένα switch statement και γιατί ενδεχομένως ένα switch να είναι προτιμότερο από ένα if statement, ας δούμε το παρακάτω σενάριο. Ας υποθέσουμε ότι θέλουμε να τυπώσουμε ένα μήνυμα σχετικά με το αν κάποια ημέρα της εβδομάδας είναι εργάσιμη ή όχι. Με ενα if statement αυτό θα μπορούσε να γίνει ως εξής:

 1const ημέρα = "Δευτέρα";
 2
 3if (ημέρα === "Δευτέρα") {
 4  console.log("εργάσιμη");
 5} else if (ημέρα === "Τρίτη") {
 6  console.log("εργάσιμη");
 7} else if (ημέρα === "Τετάρτη") {
 8  console.log("εργάσιμη");
 9} else if (ημέρα === "Πέμπτη") {
10  console.log("εργάσιμη");
11} else if (ημέρα === "Παρασκευή") {
12  console.log("εργάσιμη");
13} else if (ημέρα === "Σαββάτο") {
14  console.log("σαββατοκύριακο");
15} else if (ημέρα === "Κυριακή") {
16  console.log("σαββατοκύριακο");
17} else {
18  console.log("λάθος τιμή");
19}

Το παραπάνω κομμάτι κώδικα δουλεύει άψογα και θα καλύψει όλες μας τις ανάγκες, αλλά τα επαναλαμβανόμενα expressions που χρησιμοποιούμε σαν συνθήκες κάνουν τον κώδικά μας αρκετά “φορτωμένο” στο μάτι. Ας δούμε πως μπορούμε να λύσουμε αυτό το πρόβλημα με ένα switch statement, ακολουθώντας τον τρόπο σύνταξης που είδαμε παραπάνω:

 1const ημέρα = "Δευτέρα";
 2
 3switch (ημέρα) {
 4  case "Δευτέρα":
 5    console.log("εργάσιμη");
 6    break;
 7
 8  case "Τρίτη":
 9    console.log("εργάσιμη");
10    break;
11
12  case "Τετάρτη":
13    console.log("εργάσιμη");
14    break;
15
16  case "Πέμπτη":
17    console.log("εργάσιμη");
18    break;
19
20  case "Παρασκευή":
21    console.log("εργάσιμη");
22    break;
23
24  case "Σαββάτο":
25    console.log("σαββατοκύριακο");
26    break;
27
28  case "Κυριακή":
29    console.log("σαββατοκύριακο");
30    break;
31
32  default:
33    console.log("λάθος τιμή");
34}

Ας δούμε αναλυτικά τι συμβαίνει σε κάθε μέρος:

Αρχικά, ορίζουμε το block statement το οποιο θα ελέγχει την τιμή του expression πάνω στην οποία θα βασίσουμε το σενάριο μας–ουσιαστικά δημιουργούμε το switch statement ή το switch block:

1const ημέρα = "Δευτέρα";
2
3switch (ημέρα) {
4  case "Δευτέρα":
5    console.log("εργάσιμη");
6    break;
7
8  // ...
9}

Στην συνέχεια ορίζουμε τις περιπτώσεις που θέλουμε να καλύψουμε για κάθε τιμή κάνοντας χρήση της λέξης-κλειδί case. Αν η τιμή που επιστρέφει το expression ταιριάζει με την τιμή του case, τότε ο κώδικας που αντιστοιχεί σε αυτό το case block θα εκτελεστεί:

1const ημέρα = "Δευτέρα";
2
3switch (ημέρα) {
4  case "Δευτέρα":
5    console.log("εργάσιμη");
6    break;
7
8  // ...
9}

Παρατηρούμε ότι στο τέλος κάθε του case block υπάρχει η λέξη κλειδί break:

1const ημέρα = "Δευτέρα";
2
3switch (ημέρα) {
4  case "Δευτέρα":
5    console.log("εργάσιμη");
6    break;
7
8  // ...
9}

Τέλος, βλέπουμε ότι το τελευταίο block κώδικα αντιστοιχεί στο default statement, το οποίο αν και προαιρετικό, θεωρείται καλή πρακτική να περιλαμβάνουμε ένα default statement. Το default statement λειτουργεί ακριβώς όπως το else σε ένα if statement: αν κανένα από τα case statements δεν εκτελεστεί, τότε θα εκτελεστεί ο κώδικας default block.

 1const ημέρα = "Δευτέρα";
 2
 3switch (ημέρα) {
 4  case "Δευτέρα":
 5    console.log("εργάσιμη");
 6    break;
 7
 8  // ...
 9
10  default:
11    console.log("λάθος τιμή");
12}

Με μια πρώτη ματιά ο κωδικάς μας σχεδόν διπλασιάστηκε σε έκταση (σσ: γραμμές)! Αυτό όμως δεν είναι απαραίτητα κακό αν εξυπηρετείται ο αρχικός μας σκοπός μας: να οργανώσουμε και να κάνουμε τον κώδικά μας πιο απλό και ευανάγνωστο. Μετατρέποντας ένα if statement σε switch ξέρουμε ότι υπάρχει ένα μοναδικό expression που πρέπει να ελέγξουμε και τα σενάριά μας εξαρτόνται από την τιμή που επιστρέφει το expression κατά το evaluation. Εξ ορισμού, κάτι τέτοιο είναι πολύ πιο απλό από ένα if...else statement.

Η συμπεριφορά Fall-Through

Όπως αναφέραμε, αν ξεχάσουμε να συμπεριλάβουμε το break statement σε κάποιο case block, η εκτέλεση θα συνεχιστεί στο επόμενο case statement και μέχρι να συναντήσει κάποιο break statement ή το τέλος το switch block. Αυτή η συμπεριφορά ονομάζεται Fall-Through. Συνήθως, είναι μια συμπεριφορά που θέλουμε να αποφύγουμε, αλλά υπάρχουν περιπτώσεις όπου μπορεί να είναι χρήσιμο, ειδικά όταν θέλουμε να εκτελέσουμε τον ίδιο κώδικα για πολλαπλές τιμές.

Στο προηγούμενο μας παράδειγμα, τα case blocks για τις τιμές "Δευτέρα", "Τρίτη", "Τετάρτη", "Πέμπτη" και "Παρασκευή" ήταν τα ίδια, καθώς και για τις τιμές "Σαββάτο" και "Κυριακή". Εκμεταλλευόμενοι την συμπεριφορά Fall-Through, θα μπορούσαμε να αφαιρέσουμε τα break; statements και να συμπτύξουμε τα case statements για αυτές τις τιμές σε ένα:

 1const ημέρα = "Τρίτη";
 2
 3switch (ημέρα) {
 4  case "Δευτέρα":
 5  case "Τρίτη":
 6  case "Τετάρτη":
 7  case "Πέμπτη":
 8  case "Παρασκευή":
 9    console.log("εργάσιμη");
10    break;
11
12  case "Σάββατο":
13  case "Κυριακή":
14    console.log("σαββατοκύριακο");
15    break;
16
17  default:
18    console.log("λάθος τιμή");
19}

Εδώ, αν η ημέρα είναι "Τρίτη", δεν υπάρχει break; μετά το case "Δευτέρα", οπότε η εκτέλεση “πέφτει” στο case "Τρίτη" και συνεχίζει. Όλα τα σενάρια για τις καθημερινές ημέρες οδηγούν στο να τυπωθεί το ίδιο μήνυμα "εργάσιμη" πριν το break τερματίσει την εκτέλεση του switch statements

Θα μπορούσαμε να επιτύχουμε το ίδιο αποτέλεσμα με if..else if statements, αλλά ο κώδικάς μας θα γινόταν αρκετά πιο πολύπλοκος καθώς θα έπρεπε να διαχειριστούμε πολλαπλά expressions για μία μόνο τιμή:

 1const ημέρα = "Τρίτη";
 2
 3if (
 4  ημέρα === "Δευτέρα" ||
 5  ημέρα === "Τρίτη" ||
 6  ημέρα === "Τετάρτη" ||
 7  ημέρα === "Πέμπτη" ||
 8  ημέρα === "Παρασκευή"
 9) {
10  console.log("εργάσιμη");
11} else if (ημέρα === "Σάββατο" || ημέρα === "Κυριακή") {
12  console.log("σαββατοκύριακο");
13} else {
14  console.log("λάθος τιμή");
15}

Τί να προσέξεις

Στα παραδείγματά μας μέχρι τώρα, παρατηρήσαμε ότι το default block δεν τερμάτιζε με break statement. Αυτό συνέβη επειδή βρισκόταν στο τέλος των case blocks, ακριβώς πριν ολοκληρωθεί το switch statement. Ωστόσο, αν είχαμε τοποθετήσει το default block ανάμεσα σε άλλα case blocks χωρίς να το τερματίσουμε με ένα break statement, τότε η fall-through συμπεριφορά θα εφαρμοζόταν και σε αυτό, όπως ακριβώς και στα υπόλοιπα μπλοκ που έχουμε δει.

Ας δούμε το ακόλουθο παράδειγμα:

 1const θερμοκρασία = 35;
 2
 3switch (θερμοκρασία) {
 4  case 10:
 5    console.log("Κάνει κρύο");
 6
 7  default:
 8    console.log("Φυσιολογικός καιρός");
 9
10  case 25:
11    console.log("Κάνει ζέστη");
12
13  case 40:
14    console.log("Καύσωνας");
15}

Τα μηνύματα που θα εκτυπώσει είναι στην κονσόλα μας είναι:

  • "Φυσιολογικός καιρός"
  • "Κάνει ζέστη"
  • "Καύσωνας"

Και αυτό γιατί, με το που συνάντησε την πρώτη περίπτωση που ικανοποίησε την τιμή της μεταβλητής–στη συγκεκριμένη περίπτωση, το default statement–η εκτέλεση δεν τερματίστηκε ποτέ, καθώς δεν υπήρχε κάποιο break statement για να την σταματήσει. Ως εκ τούτου, η σειρά με την οποία δηλώνονται τα case και default statements παίζει πολύ σημαντικό ρόλο όταν εφαρμόζεται η συμπεριφορά fall-through.

Διαφορές switch και if statement

Χαρακτηριστικόswitch statementif...else if statement
Βασικός σκοπόςΕκτέλεση διαφορετικού κώδικα με βάση πολλές πιθανές τιμές μιας και μόνο έκφρασης.Εκτέλεση διαφορετικού κώδικα με βάση μία ή περισσότερες συνθήκες.
ΣύγκρισηΧρησιμοποιεί αυστηρή ισότητα (===) για σύγκριση της έκφρασης με τις τιμές των case statement.Επιτρέπει χρήση λογικών ή συγκριτικών τελεστών (π.χ., ==, ===, <, >, &&, ||).
Ευανάγνωστη γιαΠολλές πιθανές τιμές μιας και μόνο έκφρασης.Σύνθετες συνθήκες, έλεγχοι εύρους, ή όταν οι συνθήκες αφορούν διαφορετικές μεταβλητές.
Αποφυγή fall-throughΑπαιτεί break για την έξοδο από το switch μπλοκ. Αν παραλειφθεί, συμβαίνει “fall-through” στο επόμενο case statement.Δεν υπάρχει αντίστοιχο “fall-through” πρόβλημα. Κάθε if ή else if statement αξιολογείται ξεχωριστά.
Προεπιλεγμένη λογικήΧρησιμοποιεί το default block αν δεν ταιριάζει κανένα case statement .Χρησιμοποιεί το else block αν καμία if ή else if συνθήκη δεν είναι αληθής.
ΠεριορισμοίΟι τιμές των case statements πρέπει να είναι τιμές. Δεν μπορούν να περιέχουν λογική.Οι συνθήκες μπορούν να είναι οποιαδήποτε έγκυρη λογική έκφραση.