Scala wspiera mechanizm klas przypadków. Klasy przypadków są zwykłymi klasami z dodatkowymi założeniami:
- Domyślnie niemutowalne
- Można je dekomponować poprzez dopasowanie wzorca
- Porównywane poprzez podobieństwo strukturalne zamiast przez referencje
- Zwięzła składnia tworzenia obiektów i operacji na nich
Poniższy przykład obrazuje hierarchię typów powiadomień, która składa się z abstrakcyjnej klasy Notification
oraz trzech konkretnych rodzajów zaimplementowanych jako klasy przypadków Email
, SMS
i VoiceRecording
:
abstract class Notification
case class Email(sourceEmail: String, title: String, body: String) extends Notification
case class SMS(sourceNumber: String, message: String) extends Notification
case class VoiceRecording(contactName: String, link: String) extends Notification
Tworzenie obiektu jest bardzo proste: (Zwróć uwagę na to, że słowo new
nie jest wymagane)
val emailFromJohn = Email("john.doe@mail.com", "Greetings From John!", "Hello World!")
Parametry konstruktora klasy przypadków są traktowane jako publiczne wartości i można się do nich odwoływać bezpośrednio:
val title = emailFromJohn.title
println(title) // wypisuje "Greetings From John!"
W klasach przypadków nie można modyfikować wartości pól. (Z wyjątkiem sytuacji kiedy dodasz var
przed nazwą pola)
emailFromJohn.title = "Goodbye From John!" // Jest to błąd kompilacji, gdyż pola klasy przypadku są domyślnie niezmienne
Zamiast tego możesz utworzyć kopię używając metody copy
:
val editedEmail = emailFromJohn.copy(title = "I am learning Scala!", body = "It's so cool!")
println(emailFromJohn) // wypisuje "Email(john.doe@mail.com,Greetings From John!,Hello World!)"
println(editedEmail) // wypisuje "Email(john.doe@mail.com,I am learning Scala,It's so cool!)"
Dla każdej klasy przypadku kompilator Scali wygeneruje metodę equals
implementującą strukturalne porównanie obiektów oraz metodę toString
. Przykład:
val firstSms = SMS("12345", "Hello!")
val secondSms = SMS("12345", "Hello!")
if (firstSms == secondSms) {
println("They are equal!")
}
println("SMS is: " + firstSms)
Wypisze:
They are equal!
SMS is: SMS(12345, Hello!)
Jednym z najważniejszych zastosowań klas przypadków (skąd też się wzięła ich nazwa) jest dopasowanie wzorca. Poniższy przykład pokazuje działanie funkcji, która zwraca różne komunikaty w zależności od rodzaju powiadomienia:
def showNotification(notification: Notification): String = {
notification match {
case Email(email, title, _) =>
"You got an email from " + email + " with title: " + title
case SMS(number, message) =>
"You got an SMS from " + number + "! Message: " + message
case VoiceRecording(name, link) =>
"you received a Voice Recording from " + name + "! Click the link to hear it: " + link
}
}
val someSms = SMS("12345", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
println(showNotification(someSms)) // Wypisuje "You got an SMS from 12345! Message: Are you there?"
println(showNotification(someVoiceRecording)) // Wypisuje "you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123"
Poniżej bardziej skomplikowany przykład używający if
w celu określenia dodatkowych warunków dopasowania:
def showNotificationSpecial(notification: Notification, specialEmail: String, specialNumber: String): String = {
notification match {
case Email(email, _, _) if email == specialEmail =>
"You got an email from special someone!"
case SMS(number, _) if number == specialNumber =>
"You got an SMS from special someone!"
case other =>
showNotification(other) // nic szczególnego, wywołaj domyślną metodę showNotification
}
}
val SPECIAL_NUMBER = "55555"
val SPECIAL_EMAIL = "jane@mail.com"
val someSms = SMS("12345", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
val specialEmail = Email("jane@mail.com", "Drinks tonight?", "I'm free after 5!")
val specialSms = SMS("55555", "I'm here! Where are you?")
println(showNotificationSpecial(someSms, SPECIAL_EMAIL, SPECIAL_NUMBER)) // Wypisuje "You got an SMS from 12345! Message: Are you there?"
println(showNotificationSpecial(someVoiceRecording, SPECIAL_EMAIL, SPECIAL_NUMBER)) // Wypisuje "you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123"
println(showNotificationSpecial(specialEmail, SPECIAL_EMAIL, SPECIAL_NUMBER)) // Wypisuje "You got an email from special someone!"
println(showNotificationSpecial(specialSms, SPECIAL_EMAIL, SPECIAL_NUMBER)) // Wypisuje "You got an SMS from special someone!"
Programując w Scali zachęca się, abyś jak najszerzej używał klas przypadków do modelowania danych, jako że kod, który je wykorzystuje, jest bardziej ekspresywny i łatwiejszy do utrzymania:
- Obiekty niemutowalne uwalniają cię od potrzeby śledzenia zmian stanu
- Porównanie przez wartość pozwala na porównywanie instancji tak, jakby były prymitywnymi wartościami
- Dopasowanie wzorca znacząco upraszcza logikę rozgałęzień, co prowadzi do mniejszej ilości błędów i czytelniejszego kodu
Contributors to this page:
Contents
- Wprowadzenie
- Podstawy
- Hierarchia typów
- Klasy
- Cechy
- Krotki
- Kompozycja klas przez domieszki
- Funkcje wyższego rzędu
- Funkcje zagnieżdżone
- Rozwijanie funkcji (Currying)
- Klasy przypadków
- Dopasowanie wzorców (Pattern matching)
- Obiekty singleton
- Wzorce wyrażeń regularnych
- Obiekty ekstraktorów
- For Comprehensions
- Klasy generyczne
- Wariancje
- Górne ograniczenia typów
- Dolne ograniczenia typów
- Klasy wewnętrzne
- Typy abstrakcyjne
- Typy złożone
- Jawnie typowane samoreferencje
- Parametry domniemane
- Konwersje niejawne
- Metody polimorficzne
- Lokalna inferencja typów
- Operatory
- By-name Parameters
- Adnotacje
- Domyślne wartości parametrów
- Parametry nazwane
- Packages and Imports
- Package Objects